【源码剖析】tinyhttpd——C语⾔实现最简单的HTTP服务器 如有问题请在新地址提问
tinyhttpd 是⼀个不到 500 ⾏的超轻量型 Http Server,⽤来学习⾮常不错,可以帮助我们真正理解服务器程序的本质。
看完所有源码,真的感觉有很⼤收获,⽆论是 unix 的编程,还是 GET/POST 的 Web 处理流程,都清晰了不少。废话不说,开始我们的 Server 探索之旅。
(⽔平有限,如有错误之处,欢迎指正)
项⽬主页
主要函数
这是所有函数的声明:
void accept_request(int);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);
先简单地解释每个函数的作⽤:
accept_request: 处理从套接字上监听到的⼀个 HTTP 请求,在这⾥可以很⼤⼀部分地体现服务器处理请求流程。
bad_request: 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST.
cat: 读取服务器上某个⽂件写到 socket 套接字。
cannot_execute: 主要处理发⽣在执⾏ cgi 程序时出现的错误。
error_die: 把错误信息写到 perror 并退出。
execute_cgi: 运⾏ cgi 程序的处理,也是个主要函数。
get_line: 读取套接字的⼀⾏,把回车换⾏等情况都统⼀为换⾏符结束。
headers: 把 HTTP 响应的头部写到套接字。
not_found: 主要处理不到请求的⽂件时的情况。
sever_file: 调⽤ cat 把服务器⽂件返回给浏览器。
startup: 初始化 httpd 服务,包括建⽴套接字,绑定端⼝,进⾏监听等。
unimplemented: 返回给浏览器表明收到的 HTTP 请求所⽤的 method 不被⽀持。
建议源码阅读顺序: main -> startup -> accept_request -> execute_cgi, 通晓主要⼯作流程后再仔细把每个函数的源码看⼀看。
⼯作流程
(1) 服务器启动,在指定端⼝或随机选取端⼝绑定 httpd 服务。
(2)收到⼀个 HTTP 请求时(其实就是 listen 的端⼝ accpet 的时候),派⽣⼀个线程运⾏ accept_request 函数。
(3)取出 HTTP 请求中的 method (GET 或 POST) 和 url,。对于 GET ⽅法,如果有携带参数,则 query_string 指针指向 url 中 ?后⾯的 GET 参数。
(4) 格式化 url 到 path 数组,表⽰浏览器请求的服务器⽂件路径,在 tinyhttpd 中服务器⽂件是在 htdocs ⽂件夹下。当 url 以 /结尾,或 url 是个⽬录,则默认在 path 中加上 index.html,表⽰访问主页。
(5)如果⽂件路径合法,对于⽆参数的 GET 请求,直接输出服务器⽂件到浏览器,即⽤ HTTP 格式写到套接字上,跳到(10)。其他情况(带参数 GET,POST ⽅式,url 为可执⾏⽂件),则调⽤ excute_cgi 函数执⾏ cgi 脚本。
(6)读取整个 HTTP 请求并丢弃,如果是 POST 则出 Content-Length. 把 HTTP 200 状态码写到套接字。
(7) 建⽴两个管道,cgi_input 和 cgi_output, 并 fork ⼀个进程。
(8) 在⼦进程中,把 STDOUT 重定向到 cgi_outputt 的写⼊端,把 STDIN 重定向到 cgi_input 的读取端,关闭 cgi_input 的写⼊端和 cgi_output 的读取端,设置 request_method 的环境变量,GET 的话设置 query_string 的环境变量,POST 的话设置
content_length 的环境变量,这些环境变量都是为了给 cgi 脚本调⽤,接着⽤ execl 运⾏ cgi 程序。
(9) 在⽗进程中,关闭 cgi_input 的读取端 和 cgi_output 的写⼊端,如果 POST 的话,把 POST 数据写⼊ cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输⼊是 STDOUT。接着关闭所有管道,等待⼦进程结束。这⼀部分⽐较乱,见下图说明:
图 1 管道初始状态
图 2 管道最终状态
(10) 关闭与浏览器的连接,完成了⼀次 HTTP 请求与回应,因为 HTTP 是⽆连接的。
注释版源码
源码已写了注释,放在 Github:
懒得跳转的同学看下⾯....
/* J. David's webserver */
/* This is a simple webserver.
* Created November 1999 by J. David Blackstone.
* CSE 4344 (Network concepts), Prof. Zeigler
* University of Texas at Arlington
*/
/* This program compiles for Sparc Solaris 2.6.
* To compile for Linux:
* 1) Comment out the #include <pthread.h> line.
* 2) Comment out the line that defines the variable newthread.
* 3) Comment out the two lines that run pthread_create().
* 4) Uncomment the line that runs accept_request().
* 5) Remove -lsocket from the Makefile.
*/
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
void accept_request(int);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int);
/**********************************************************************/
/
* A request has caused a call to accept() on the server port to
* return. Process the request appropriately.
* Parameters: the socket connected to the client */
/**********************************************************************/
void accept_request(int client)
{
char buf[1024];
int numchars;
char method[255];
char url[255];
char path[512];
size_t i, j;
struct stat st;
int cgi = 0; /* becomes true if server decides this is a CGI program */ char *query_string = NULL;
/*得到请求的第⼀⾏*/
numchars = get_line(client, buf, sizeof(buf));
i = 0; j = 0;
/*把客户端的请求⽅法存到 method 数组*/
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0';
/*如果既不是 GET ⼜不是 POST 则⽆法处理 */
if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return;
}
/* POST 的时候开启 cgi */
if (strcasecmp(method, "POST") == 0)
cgi = 1;
/
*读取 url 地址*/
i = 0;
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
{
/*存下 url */
url[i] = buf[j];
i++; j++;
}
url[i] = '\0';
/
*处理 GET ⽅法*/
if (strcasecmp(method, "GET") == 0)
{
/* 待处理请求为 url */
query_string = url;
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))html导航源码
query_string++;
/* GET ⽅法特点,? 后⾯为参数*/
if (*query_string == '?')
{
/
*开启 cgi */
cgi = 1;
*query_string = '\0';
query_string++;
}
}
/*格式化 url 到 path 数组,html ⽂件都在 htdocs 中*/
sprintf(path, "htdocs%s", url);
/*默认情况为 index.html */
if (path[strlen(path) - 1] == '/')
strcat(path, "index.html");
/
*根据路径到对应⽂件 */
if (stat(path, &st) == -1) {
/*把所有 headers 的信息都丢弃*/
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
/*回应客户端不到*/
not_found(client);
}
else
{
/*如果是个⽬录,则默认使⽤该⽬录下 index.html ⽂件*/
if ((st.st_mode & S_IFMT) == S_IFDIR)
strcat(path, "/index.html");
if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) cgi = 1;
/*不是 cgi,直接把服务器⽂件返回,否则执⾏ cgi */
if (!cgi)
serve_file(client, path);
else
execute_cgi(client, path, method, query_string);
}
/*断开与客户端的连接(HTTP 特点:⽆连接)*/
close(client);
}
/**********************************************************************/
/* Inform the client that a request it has made has a problem.
* Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{
char buf[1024];
/*回应客户端错误的 HTTP 请求 */
sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "<P>Your browser sent a bad request, ");
send(client, buf, sizeof(buf), 0);
sprintf(buf, "such as a POST without a Content-Length.\r\n");
send(client, buf, sizeof(buf), 0);
}
/**********************************************************************/
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论