Created
June 26, 2013 05:10
-
-
Save eagleon/5864933 to your computer and use it in GitHub Desktop.
small HTTP server : 200行
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* micro_httpd - really small HTTP server | |
| ** | |
| ** Copyright (c) 1999,2005 by Jef Poskanzer <jef@mail.acme.com>. | |
| ** All rights reserved. | |
| ** | |
| ** Redistribution and use in source and binary forms, with or without | |
| ** modification, are permitted provided that the following conditions | |
| ** are met: | |
| ** 1. Redistributions of source code must retain the above copyright | |
| ** notice, this list of conditions and the following disclaimer. | |
| ** 2. Redistributions in binary form must reproduce the above copyright | |
| ** notice, this list of conditions and the following disclaimer in the | |
| ** documentation and/or other materials provided with the distribution. | |
| ** | |
| ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
| ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
| ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
| ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| ** SUCH DAMAGE. | |
| */ | |
| /* | |
| ####################################### | |
| 1.此程序对输入输出使用的是stdin和stdout | |
| 借助xinetd,microhttpd在处理请求时,可从标准输入读取客户端的请求,向标准输出写入对客户端的响应。所以micro_httpd的核心在于分析http请求参数,并发送响应数据。 | |
| 2.此程序没有创建socket服务 | |
| 需要其它程序为其创建socket服务 | |
| 特点: | |
| * 支持安全的 .. 上级目录过滤 | |
| * 支持通用的MIME类型 | |
| * 支持简单的目录 | |
| * 支持目录列表 | |
| * 支持使用 index.html 作为首页 | |
| * Trailing-slash redirection | |
| ###################################### | |
| */ | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <dirent.h> | |
| #include <ctype.h> | |
| #include <time.h> | |
| #include <sys/stat.h> | |
| #define SERVER_NAME "micro_httpd" | |
| #define SERVER_URL "http://208.115.207.24:8090/" | |
| #define PROTOCOL "HTTP/1.0" | |
| #define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT" | |
| /* Forwards. */ | |
| //输出文件相关属性的详细信息 | |
| static void file_details( char* dir, char* name ); | |
| //返回出错页面 | |
| static void send_error( int status, char* title, char* extra_header, char* text ); | |
| //设置reponse | |
| static void send_headers( int status, char* title, char* extra_header, char* mime_type, off_t length, time_t mod ); | |
| //通过文件名推断文件mime_type | |
| static char* get_mime_type( char* name ); | |
| //URL 解码 | |
| static void strdecode( char* to, char* from ); | |
| //十六进制转换为十进制数 | |
| static int hexit( char c ); | |
| //用于文件名编码 | |
| static void strencode( char* to, size_t tosize, const char* from ); | |
| int main( int argc, char** argv ) | |
| { | |
| char line[10000], method[10000], path[10000], protocol[10000], idx[20000], location[20000], command[20000]; | |
| char* file; | |
| size_t len; | |
| int ich; | |
| struct stat sb; | |
| FILE* fp; | |
| struct dirent **dl; | |
| int i, n; | |
| if ( argc != 2 ) //如果没有指明根目录,xinetd中的server_args | |
| send_error( 500, "Internal Error", (char*) 0, "Config error - no dir specified." ); | |
| //更改工作目录 | |
| if ( chdir( argv[1] ) < 0 ) //切换目录失败 | |
| send_error( 500, "Internal Error", (char*) 0, "Config error - couldn't chdir()." ); | |
| if ( fgets( line, sizeof(line), stdin ) == (char*) 0 ) //提取请求行,包含请求方法、路径、协议 | |
| send_error( 400, "Bad Request", (char*) 0, "No request found." ); | |
| //解析请求 形如 GET /abc/index.html HTTP/1.0 | |
| if ( sscanf( line, "%[^ ] %[^ ] %[^ ]", method, path, protocol ) != 3 ) //提取方法、路径、协议 | |
| send_error( 400, "Bad Request", (char*) 0, "Can't parse request." ); | |
| while ( fgets( line, sizeof(line), stdin ) != (char*) 0 ) // 读到请求头结束 | |
| { | |
| if ( strcmp( line, "\n" ) == 0 || strcmp( line, "\r\n" ) == 0 ) | |
| break; | |
| } | |
| if ( strcasecmp( method, "get" ) != 0 ) // micro_httpd只支持get请求 | |
| send_error( 501, "Not Implemented", (char*) 0, "That method is not implemented." ); | |
| if ( path[0] != '/' ) // 路径名不以斜杠开头 | |
| send_error( 400, "Bad Request", (char*) 0, "Bad filename." ); | |
| file = &(path[1]); | |
| strdecode( file, file ); // 解码路径名,相对于micro_httpd的根目录argv[1] | |
| if ( file[0] == '\0' ) | |
| file = "./"; | |
| len = strlen( file ); // 检查路径名是否会越界访问到根目录上级目录,..snoop | |
| if ( file[0] == '/' || strcmp( file, ".." ) == 0 || strncmp( file, "../", 3 ) == 0 || strstr( file, "/../" ) != (char*) 0 || strcmp( &(file[len-3]), "/.." ) == 0 ) | |
| send_error( 400, "Bad Request", (char*) 0, "Illegal filename." ); | |
| if ( stat( file, &sb ) < 0 ) // 访问的文件(或目录)不存在 | |
| send_error( 404, "Not Found", (char*) 0, "File not found." ); | |
| //请求路径是文件夹吗 | |
| if ( S_ISDIR( sb.st_mode ) ) // 路径名对应目录 | |
| { // 如果文件名最后不是以 '/'为结尾的。如这样的 GET /index/default HTTP/1.1 。而他也是一个目录。 | |
| if ( file[len-1] != '/' ) // 目录名要以斜杠结尾 | |
| { | |
| (void) snprintf( location, sizeof(location), "Location: %s/", path ); | |
| send_error( 302, "Found", location, "Directories must end with a slash." ); | |
| } // 如果有则访问本目录下index.html文件 。并给变量 idx 给值,并给了完正的文件全名 | |
| (void) snprintf( idx, sizeof(idx), "%sindex.html", file ); | |
| if ( stat( idx, &sb ) >= 0 ) // 目录之下存在index.html文件,则返回index.html的内容 | |
| { // 在判断一次如果文件有效则输出,如果不成功,正明本目录没有index.html文件 。 | |
| file = idx; | |
| goto do_file; | |
| } | |
| //显示请求文件夹下的文件列表 | |
| send_headers( 200, "Ok", (char*) 0, "text/html", -1, sb.st_mtime ); | |
| (void) printf( "<html><head><title>Index of %s</title></head>\n<body bgcolor=\"#99cc99\"><h4>Index of %s</h4>\n<pre>\n", file, file ); | |
| n = scandir( file, &dl, NULL, alphasort ); // 读取目录下的各个目录项,并返回每个文件的信息 | |
| if ( n < 0 ) // 扫喵本目录,成功返回目录下 文件数目,如果小于0系统报错。 | |
| perror( "scandir" ); | |
| else | |
| for ( i = 0; i < n; ++i ) | |
| file_details( file, dl[i]->d_name ); | |
| (void) printf( "</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n", SERVER_URL, SERVER_NAME ); | |
| } | |
| else | |
| { //返回请求的文件 | |
| do_file: // 获取文件的内容并返回给客户端 | |
| fp = fopen( file, "r" ); | |
| if ( fp == (FILE*) 0 ) | |
| send_error( 403, "Forbidden", (char*) 0, "File is protected." ); | |
| send_headers( 200, "Ok", (char*) 0, get_mime_type( file ), sb.st_size, sb.st_mtime ); | |
| while ( ( ich = getc( fp ) ) != EOF ) // 向标准输出中输出index.html内容 | |
| putchar( ich ); | |
| } | |
| (void) fflush( stdout ); | |
| exit( 0 ); | |
| } | |
| /* 获取文件的信息,并响应文件的超链接信息 */ | |
| //输出文件相关属性的详细信息 | |
| static void | |
| file_details( char* dir, char* name ) | |
| { | |
| static char encoded_name[1000]; | |
| static char path[2000]; | |
| struct stat sb; | |
| char timestr[16]; | |
| strencode( encoded_name, sizeof(encoded_name), name ); | |
| (void) snprintf( path, sizeof(path), "%s/%s", dir, name ); | |
| if ( lstat( path, &sb ) < 0 ) | |
| (void) printf( "<a href=\"%s\">%-32.32s</a> ???\n", encoded_name, name ); | |
| else | |
| { | |
| (void) strftime( timestr, sizeof(timestr), "%d%b%Y %H:%M", localtime( &sb.st_mtime ) ); | |
| (void) printf( "<a href=\"%s\">%-32.32s</a> %15s %14lld\n", encoded_name, name, timestr, (int64_t) sb.st_size ); | |
| } | |
| } | |
| //返回出错页面 /* 出错时,发送错误响应信息 */ | |
| static void | |
| send_error( int status, char* title, char* extra_header, char* text ) | |
| { | |
| send_headers( status, title, extra_header, "text/html", -1, -1 ); | |
| (void) printf( "<html><head><title>%d %s</title></head>\n<body bgcolor=\"#cc9999\"><h4>%d %s</h4>\n", status, title, status, title ); | |
| (void) printf( "%s\n", text ); | |
| (void) printf( "<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n", SERVER_URL, SERVER_NAME ); | |
| (void) fflush( stdout ); | |
| exit( 1 ); | |
| } | |
| //设置reponse header /* 发送http响应头部信息 */ | |
| //状态 标题 mime_type 文件最后被修改的时间 | |
| static void | |
| send_headers( int status, char* title, char* extra_header, char* mime_type, off_t length, time_t mod ) | |
| { | |
| time_t now; | |
| char timebuf[100]; | |
| //\015\012 回车换行 | |
| (void) printf( "%s %d %s\015\012", PROTOCOL, status, title ); | |
| (void) printf( "Server: %s\015\012", SERVER_NAME ); | |
| now = time( (time_t*) 0 ); | |
| //将时间格式化后存在timebuf中 | |
| (void) strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime( &now ) ); | |
| (void) printf( "Date: %s\015\012", timebuf ); | |
| if ( extra_header != (char*) 0 ) | |
| (void) printf( "%s\015\012", extra_header ); | |
| if ( mime_type != (char*) 0 ) | |
| (void) printf( "Content-Type: %s\015\012", mime_type ); | |
| if ( length >= 0 ) | |
| (void) printf( "Content-Length: %lld\015\012", (int64_t) length ); | |
| if ( mod != (time_t) -1 ) | |
| { | |
| (void) strftime( timebuf, sizeof(timebuf), RFC1123FMT, gmtime( &mod ) ); | |
| (void) printf( "Last-Modified: %s\015\012", timebuf ); | |
| } | |
| (void) printf( "Connection: close\015\012" ); | |
| (void) printf( "\015\012" ); | |
| } | |
| //通过文件名推断文件mime_type /* 根据文件名获取mime类型,通过分析文件后缀名 */ | |
| static char* | |
| get_mime_type( char* name ) | |
| { | |
| char* dot; | |
| dot = strrchr( name, '.' ); | |
| if ( dot == (char*) 0 ) | |
| return "text/plain; charset=iso-8859-1"; | |
| if ( strcmp( dot, ".html" ) == 0 || strcmp( dot, ".htm" ) == 0 ) | |
| return "text/html; charset=iso-8859-1"; | |
| if ( strcmp( dot, ".jpg" ) == 0 || strcmp( dot, ".jpeg" ) == 0 ) | |
| return "image/jpeg"; | |
| if ( strcmp( dot, ".gif" ) == 0 ) | |
| return "image/gif"; | |
| if ( strcmp( dot, ".png" ) == 0 ) | |
| return "image/png"; | |
| if ( strcmp( dot, ".css" ) == 0 ) | |
| return "text/css"; | |
| if ( strcmp( dot, ".au" ) == 0 ) | |
| return "audio/basic"; | |
| if ( strcmp( dot, ".wav" ) == 0 ) | |
| return "audio/wav"; | |
| if ( strcmp( dot, ".avi" ) == 0 ) | |
| return "video/x-msvideo"; | |
| if ( strcmp( dot, ".mov" ) == 0 || strcmp( dot, ".qt" ) == 0 ) | |
| return "video/quicktime"; | |
| if ( strcmp( dot, ".mpeg" ) == 0 || strcmp( dot, ".mpe" ) == 0 ) | |
| return "video/mpeg"; | |
| if ( strcmp( dot, ".vrml" ) == 0 || strcmp( dot, ".wrl" ) == 0 ) | |
| return "model/vrml"; | |
| if ( strcmp( dot, ".midi" ) == 0 || strcmp( dot, ".mid" ) == 0 ) | |
| return "audio/midi"; | |
| if ( strcmp( dot, ".mp3" ) == 0 ) | |
| return "audio/mpeg"; | |
| if ( strcmp( dot, ".ogg" ) == 0 ) | |
| return "application/ogg"; | |
| if ( strcmp( dot, ".pac" ) == 0 ) | |
| return "application/x-ns-proxy-autoconfig"; | |
| return "text/plain; charset=iso-8859-1"; | |
| } | |
| //用于URL 解码 /* 对路径名解码 */ | |
| static void | |
| strdecode( char* to, char* from ) | |
| { | |
| for ( ; *from != '\0'; ++to, ++from ) | |
| { | |
| if ( from[0] == '%' && isxdigit( from[1] ) && isxdigit( from[2] ) ) | |
| { | |
| *to = hexit( from[1] ) * 16 + hexit( from[2] ); | |
| from += 2; | |
| } | |
| else | |
| *to = *from; | |
| } | |
| *to = '\0'; | |
| } | |
| //十六进制转换为十进制数 | |
| static int | |
| hexit( char c ) | |
| { | |
| if ( c >= '0' && c <= '9' ) | |
| return c - '0'; | |
| if ( c >= 'a' && c <= 'f' ) | |
| return c - 'a' + 10; | |
| if ( c >= 'A' && c <= 'F' ) | |
| return c - 'A' + 10; | |
| return 0; /* shouldn't happen, we're guarded by isxdigit() */ | |
| } | |
| //用于文件名编码 /* 对路径名编码,URL encoding*/ | |
| static void | |
| strencode( char* to, size_t tosize, const char* from ) | |
| { | |
| int tolen; | |
| for ( tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from ) | |
| { | |
| //ASCII 字符、数字 或者 "/_.-~" 中的一个 | |
| if ( isalnum(*from) || strchr( "/_.-~", *from ) != (char*) 0 ) | |
| { | |
| *to = *from; | |
| ++to; | |
| ++tolen; | |
| } | |
| else //对特殊字符进行编码 问题:什么样的特殊字符需要这样编码? | |
| { | |
| (void) sprintf( to, "%%%02x", (int) *from & 0xff ); | |
| to += 3; | |
| tolen += 3; | |
| } | |
| } | |
| *to = '\0'; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment