GithubHelp home page GithubHelp logo

ezlippi / tinyhttpd Goto Github PK

View Code? Open in Web Editor NEW
11.2K 308.0 3.9K 45 KB

Tinyhttpd 是J. David Blackstone在1999年写的一个不到 500 行的超轻量型 Http Server,用来学习非常不错,可以帮助我们真正理解服务器程序的本质。官网:http://tinyhttpd.sourceforge.net

License: GNU General Public License v3.0

Makefile 0.87% Perl 4.41% HTML 1.20% C 93.52%

tinyhttpd's Introduction

A mirror for tinyhttpd(Tinyhttpd非官方镜像,Fork自sourceForge,仅供学习)

测试CGI时需要本机安装PERL,同时安装perl-cgi

Prepare

Compile for Linux

 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.

     每个函数的作用:

     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 是无连接的。


以下内容来自源作者:

This software is copyright 1999 by J. David Blackstone. Permission is granted to redistribute and modify this software under the terms of the GNU General Public License, available at http://www.gnu.org/ .

If you use this software or examine the code, I would appreciate knowing and would be overjoyed to hear about it at [email protected] .

This software is not production quality. It comes with no warranty of any kind, not even an implied warranty of fitness for a particular purpose. I am not responsible for the damage that will likely result if you use this software on your computer system.

I wrote this webserver for an assignment in my networking class in 1999. We were told that at a bare minimum the server had to serve pages, and told that we would get extra credit for doing "extras." Perl had introduced me to a whole lot of UNIX functionality (I learned sockets and fork from Perl!), and O'Reilly's lion book on UNIX system calls plus O'Reilly's books on CGI and writing web clients in Perl got me thinking and I realized I could make my webserver support CGI with little trouble.

Now, if you're a member of the Apache core group, you might not be impressed. But my professor was blown over. Try the color.cgi sample script and type in "chartreuse." Made me seem smarter than I am, at any rate. :)

Apache it's not. But I do hope that this program is a good educational tool for those interested in http/socket programming, as well as UNIX system calls. (There's some textbook uses of pipes, environment variables, forks, and so on.)

One last thing: if you look at my webserver or (are you out of mind?!?) use it, I would just be overjoyed to hear about it. Please email me. I probably won't really be releasing major updates, but if I help you learn something, I'd love to know!

Happy hacking!

                               J. David Blackstone

tinyhttpd's People

Contributors

ezlippi avatar huntinux avatar kingofctrl avatar mekayangyi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tinyhttpd's Issues

如果在buf在recv中没有收到任何消息,即buf[0] = '\n'

numchars = get_line(client, buf, sizeof(buf));
i = 0; j = 0;
while (!ISspace(buf[i]) && (i < sizeof(method) - 1))
{
method[i] = buf[i];
i++;
}
....
if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return;
}
此时直接return会发生管道破裂,内核会给进程发送signal SIGPIPE, Broken pipe消息,进程终结。所以在return之前要关闭accept函数的fd

accept_request中直接将指针赋给client

main函数中 ”accept_request(&client_sock);“ 将地址传入。
accept_request函数中 ”int client = (intptr_t)arg;“ 将地址赋给client,将导致recv读不到数据。

cat函数在读取文件的字符数小于fget的单次读取数时出现的问题

问题

void cat(int client, FILE *resource)
{
    char buf[1024];

    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
}

cat 函数在读取长度小于 1024 的文件时,在第一次 fgets 后 resourse 就已经被设为 eof 了,因此也无法进入循环 send 了。

测试

test.txt

hello world

a.c

#include <stdio.h>

#define MAXLEN 20

int main() {
    FILE *fp;
    char str[MAXLEN];

    fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("ERROR OPEN FILE");
        return -1;
    }

    if (fgets(str, sizeof(str), fp) != NULL) {
        puts(str);
    }

    if (feof(fp)) {
        puts("eof");
    } else {
        puts("no eof");
    }
    fclose(fp);

    return 0;
}

执行结果

image

有缺陷

在accept_request函数中,sprintf(path, "htdocs%s", url);明显是有问题的,htdocs后面没有‘/’,连接url会有问题。

执行make all报错

执行make all报错了

gcc -g -W -Wall -lpthread  -o httpd httpd.c
httpd.c: In function ‘execute_cgi’:
httpd.c:281:9: warning: null argument where non-null required (argument 2) [-Wnonnull]
         execl(path, NULL);
         ^~~~~
httpd.c:281:9: warning: not enough variable arguments to fit a sentinel [-Wformat=]
/tmp/cck2ie6l.o:在函数‘main’中:
/home/zhujianchen/work/git/Tinyhttpd/httpd.c:503:对‘pthread_create’未定义的引用
collect2: error: ld returned 1 exit status
make: *** [Makefile:4:httpd] 错误 1

您好,请问这是啥问题了

输入127.0.0.1后浏览器一直加载以及输入颜色后没有显示的解决方案

首先是网上说的将index.html的访问权限改为不可执行,参考第一个评论https://zhuanlan.zhihu.com/p/105656761
但是还是浏览器运行后一直显示加载,我使用gdb调试后发现,clint_sock本来是4但是在传入accept_request之后,赋给client的值是-8764
image
此时我注意到了intptr_t并查阅了相关的资料,发现这个类型的大小等于机器的位数,由此保证void转换成立,但是我编写了一个程序发现这个转化后的值等于指针的值而不是client的值,因此我将int client=(intptr_t)arg;改为了int client=(int*)arg;使得浏览器可以访问,但是这个用法不知道是哪里出现了问题。
image
最后输入颜色没有显示的问题是由于perl路径设置错误,输入which perl获得perl的路径,在check.cgi和color.cgi中的第一行的路径改为自己的就可以了。
希望可以帮到一些和我遇到同样问题的人

accept_request函数BUG

void accept_request(void *arg)
{
    int client = (intptr_t)arg;
    ......
}

intptr_t转换后的client不是传入的值。
系统版本:Ubuntu 16.04 LTS

waitpid函数为何放在最后?不应该放在send前吗?

	//waitpid(pid, &status, 0);我觉得应该放在这里啊
	 
    while (read(cgi_output[0], &c, 1) > 0)
        send(client, &c, 1, 0);

    close(cgi_output[0]);
    close(cgi_input[1]);

	/*此处存在疑问,不应该将waitpid放在前面吗?以确保子进程先运行*/
    waitpid(pid, &status, 0);

waitpid放在最后如何保证子进程结束后可以send给client?

运行./httpd后在地址栏输入127.0.0.1:4000没有响应

下面的是我在主机Ubuntu Kylin 16.04 LTS上编译信息,我已经按照说明注释了该注释的部分和需要去除注释的部分,但是依然有warning,而且在地址栏输入127.0.0.1:4000时,没有那个index响应。

我是一个初学者,所以还不理解源代码。我根据Google的结果,参照qiyeboy的Tinyhttpd
execl(path, NULL) 修改为 execl(path, path, NULL),虽然编译httpd.c时没有warming了,但是输入127.0.0.1:4000依然没有响应。还望指点一下。

gcc -g -W -Wall -lpthread  -o httpd httpd.c

httpd.c: In function ‘execute_cgi’:
httpd.c:282:9: warning: null argument where non-null required (argument 2) [-Wnonnull]
         execl(path, NULL);
         ^
httpd.c:282:9: warning: not enough variable arguments to fit a sentinel [-Wformat=]

gcc -W -Wall -o client simpleclient.c
simpleclient.c: In function ‘main’:
simpleclient.c:26:9: warning: implicit declaration of function ‘exit’ [-Wimplicit-function-declaration]
         exit(1);
         ^
simpleclient.c:26:9: warning: incompatible implicit declaration of built-in function ‘exit’
simpleclient.c:26:9: note: include ‘<stdlib.h>’ or provide a declaration of ‘exit’
simpleclient.c:32:5: warning: incompatible implicit declaration of built-in function ‘exit’
     exit(0);
     ^
simpleclient.c:32:5: note: include ‘<stdlib.h>’ or provide a declaration of ‘exit’
simpleclient.c:8:14: warning: unused parameter ‘argc’ [-Wunused-parameter]
 int main(int argc, char *argv[])
              ^
simpleclient.c:8:26: warning: unused parameter ‘argv’ [-Wunused-parameter]
 int main(int argc, char *argv[])
                          ^

mac 怎么安装perl的cgi

perl -Tw color.cgi
Can't locate CGI.pm in @inc (you may need to install the CGI module) (@inc contains: /usr/local/Cellar/perl/5.34.0/lib/perl5/site_perl/5.34.0/darwin-thread-multi-2level /usr/local/Cellar/perl/5.34.0/lib/perl5/site_perl/5.34.0 /usr/local/Cellar/perl/5.34.0/lib/perl5/5.34.0/darwin-thread-multi-2level /usr/local/Cellar/perl/5.34.0/lib/perl5/5.34.0 /usr/local/lib/perl5/site_perl/5.34.0) at color.cgi line 4.perimeter81 persepolis-download-manager
BEGIN failed--compilation aborted at color.cgi line 4.

好像找不到cgi相关的东西,导致cgi脚本报错

浏览器无法显示网页

按照注释修改代码,编译,运行./httpd后,浏览器中输入127.0.0.1:4000,一直在加载无法显示index.html网页。

image

execute_cgi中父子进程建立管道的作用

相关代码如下:
` if (pid == 0) /* child: CGI script */
{
char meth_env[255];
char query_env[255];
char length_env[255];

    dup2(cgi_output[1], STDOUT);
    dup2(cgi_input[0], STDIN);
    close(cgi_output[0]);
    close(cgi_input[1]);
    sprintf(meth_env, "REQUEST_METHOD=%s", method);
    putenv(meth_env);
    if (strcasecmp(method, "GET") == 0) {
        sprintf(query_env, "QUERY_STRING=%s", query_string);
        putenv(query_env);
    }
    else {   /* POST */
        sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
        putenv(length_env);
    }
    execl(path, NULL);
    exit(0);
} else {    /* parent */
    close(cgi_output[1]);
    close(cgi_input[0]);
    if (strcasecmp(method, "POST") == 0)
        for (i = 0; i < content_length; i++) {
            recv(client, &c, 1, 0);
            write(cgi_input[1], &c, 1);
        }
    while (read(cgi_output[0], &c, 1) > 0)
        send(client, &c, 1, 0);

    close(cgi_output[0]);
    close(cgi_input[1]);
    waitpid(pid, &status, 0);
}`

这段程序功能大致了解:子进程执行cgi脚本,父进程从client的post请求中读取数据,如果output中有输出,则返回给client。

不清楚的是:input和output管道作用是什么?子进程为何使用dup复制output[1]到标准输出,复制input[0]到标准输入?

无道理的猜测:父进程将post中请求数据传入cgi脚本,子进程执行完成后,若有数据,则写入output[0]中,父进程从中读取。

哪位大佬路过解答一波~

在虚拟机运行httpd,然后在windows浏览器中访问

自己在运行项目时遇到了一些问题,现在已经解决。

问题
成功运行httpd后,可以通过命令curl -v localhost:4000/index.htmlcurl -v localhost:4000/color.cgi从服务端获得回应,但无法通过windows浏览器访问服务端。
解决
1、设置防火墙开放相应端口firewall-cmd --add-port=4000/tcp。这里修改了代码,将端口固定为4000而不是让系统随机分配。
2、使用whereis perl命令获得perl程序的地址,然后vim htdocs/color.cgi,将第一行的perl地址改为自己查到的路径。

在windows浏览器中输入(自己的虚拟机ip):4000,可成功访问index.html界面,输入颜色可获得color.cgi的运行结果。

内存不断变大

我本地是MAC ,启动tinyhttpd后,用postman或者浏览器不断发送请求,用activity monitor观察内存不断变大,如果持续下去内存岂不是要爆掉,如何优化呢?

线程之间的竞争

在main函数中,传递给线程的参数client_sock是一个local variable,会产生竞争吗?
if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)&client_sock) != 0) perror("pthread_create");

比如连接1对应的线程运行前,连接2把client_sock修改了。
是否应该在堆上分配

你要发就发1999年的源码别乱改好不好

在execute_cgi()函数中,你在fork()之后 调用sprintf(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);这会导致这个信息发了两次,发代码之前都不用一下的吗?

startup函数中,getsockname检查、请求分配端口为何要在bind后?

if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
if (port == 0) / if dynamically allocating a port */
{
socklen_t namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
}

为什么不在bind前判断端口是否为0,为0就调用getsockname让系统自动分配。
现在在bind后,如果bind的端口为0,bind失败、分配了也没用,代码里也没有重新bind。

cat函数没有将最后一行发送过去

`void cat(int client, FILE *resource)
{
char buf[1024];

fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
    send(client, buf, strlen(buf), 0);
    fgets(buf, sizeof(buf), resource);//文件的最后一行并没有发送出去
}

}`

参数传递

pthread_create(&newthread , NULL, (void *)accept_request, (void *)&client_sock ), 这里有问题吧, 你的client_sock地址指向的内容在下一次请求接收时,会发生变化.

一定要discard headers么?

sprintf(path, "htdocs%s", url);
if (path[strlen(path) - 1] == '/')
    strcat(path, "index.html");
if (stat(path, &st) == -1) {
    while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
        numchars = get_line(client, buf, sizeof(buf));
    not_found(client);
}

这里discard headers是一定需要的么?

execute_cgi中设置环境变量的作用是什么?

初学,
源码部门如下

char meth_env[255];
char query_env[255];
char length_env[255];
 ,,,
sprintf(meth_env, "REQUEST_METHOD=%s", method);
putenv(meth_env);
if (strcasecmp(method, "GET") == 0) {
     sprintf(query_env, "QUERY_STRING=%s", query_string);
     putenv(query_env);
}
else {   /* POST */
     sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
     putenv(length_env);
}

请问这些环境变量有什么作用?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.