tinyhttpd服务器源码学习
下载地址:http://sourceforge.net/projects/tinyhttpd/
/* 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 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[];
int numchars;
char method[];
char url[];
char path[];
size_t i, j;
struct stat st;
int cgi = ; /* becomes true if server decides this is a CGI
* program */
char *query_string = NULL; numchars = get_line(client, buf, sizeof(buf));
i = ; j = ;
while (!ISspace(buf[j]) && (i < sizeof(method) - ))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0'; if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return;
} if (strcasecmp(method, "POST") == )
{
cgi = ;
} i = ;
while (ISspace(buf[j]) && (j < sizeof(buf)))
{
j++;
}
while (!ISspace(buf[j]) && (i < sizeof(url) - ) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '\0'; if (strcasecmp(method, "GET") == )
{
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
{
query_string++;
}
if (*query_string == '?')
{
cgi = ;
*query_string = '\0';
query_string++;
}
} sprintf(path, "htdocs%s", url);
if (path[strlen(path) - ] == '/')
{
strcat(path, "index.html");
}
if (stat(path, &st) == -)
{
while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
{
numchars = get_line(client, buf, sizeof(buf));
}
not_found(client);
}
else
{
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 = ;
if (!cgi)
serve_file(client, path);
else
execute_cgi(client, path, method, query_string);
} close(client);
} /**********************************************************************/
/* Inform the client that a request it has made has a problem.
* Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "<P>Your browser sent a bad request, ");
send(client, buf, sizeof(buf), );
sprintf(buf, "such as a POST without a Content-Length.\r\n");
send(client, buf, sizeof(buf), );
} /**********************************************************************/
/* Put the entire contents of a file out on a socket. This function
* is named after the UNIX "cat" command, because it might have been
* easier just to do something like pipe, fork, and exec("cat").
* Parameters: the client socket descriptor
* FILE pointer for the file to cat */
/**********************************************************************/
void cat(int client, FILE *resource)
{
char buf[]; fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
send(client, buf, strlen(buf), );
fgets(buf, sizeof(buf), resource);
}
} /**********************************************************************/
/* Inform the client that a CGI script could not be executed.
* Parameter: the client socket descriptor. */
/**********************************************************************/
void cannot_execute(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/
/* Print out an error message with perror() (for system errors; based
* on value of errno, which indicates system call errors) and exit the
* program indicating an error. */
/**********************************************************************/
void error_die(const char *sc)
{
perror(sc);
exit();
} /**********************************************************************/
/* Execute a CGI script. Will need to set environment variables as
* appropriate.
* Parameters: client socket descriptor
* path to the CGI script */
/**********************************************************************/
void execute_cgi(int client, const char *path,
const char *method, const char *query_string)
{
char buf[];
int cgi_output[];
int cgi_input[];
pid_t pid;
int status;
int i;
char c;
int numchars = ;
int content_length = -; buf[] = 'A'; buf[] = '\0';
if (strcasecmp(method, "GET") == )
{
while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
{
numchars = get_line(client, buf, sizeof(buf));
}
}
else /* POST */
{
numchars = get_line(client, buf, sizeof(buf));
while ((numchars > ) && strcmp("\n", buf))
{
buf[] = '\0';
if (strcasecmp(buf, "Content-Length:") == )
content_length = atoi(&(buf[]));
numchars = get_line(client, buf, sizeof(buf));
}
if (content_length == -) {
bad_request(client);
return;
}
} sprintf(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), ); if (pipe(cgi_output) < ) {
cannot_execute(client);
return;
}
if (pipe(cgi_input) < ) {
cannot_execute(client);
return;
} if ((pid = fork()) < ) {
cannot_execute(client);
return;
}
if (pid == ) /* child: CGI script */
{
char meth_env[];
char query_env[];
char length_env[]; dup2(cgi_output[], );
dup2(cgi_input[], );
close(cgi_output[]);
close(cgi_input[]);
sprintf(meth_env, "REQUEST_METHOD=%s", method);
putenv(meth_env);
if (strcasecmp(method, "GET") == ) {
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, path, NULL);
exit();
}
else { /* parent */
close(cgi_output[]);
close(cgi_input[]);
if (strcasecmp(method, "POST") == )
for (i = ; i < content_length; i++) {
recv(client, &c, , );
write(cgi_input[], &c, );
}
while (read(cgi_output[], &c, ) > )
send(client, &c, , ); close(cgi_output[]);
close(cgi_input[]);
waitpid(pid, &status, );
}
} /**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
* carriage return, or a CRLF combination. Terminates the string read
* with a null character. If no newline indicator is found before the
* end of the buffer, the string is terminated with a null. If any of
* the above three line terminators is read, the last character of the
* string will be a linefeed and the string will be terminated with a
* null character.
* Parameters: the socket descriptor
* the buffer to save the data in
* the size of the buffer
* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{
int i = ;
char c = '\0';
int n; while ((i < size - ) && (c != '\n'))
{
n = recv(sock, &c, , );
/* DEBUG printf("%02X\n", c); */
if (n > )
{
if (c == '\r')
{
n = recv(sock, &c, , MSG_PEEK);
/* DEBUG printf("%02X\n", c); */
if ((n > ) && (c == '\n'))
recv(sock, &c, , );
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0'; return(i);
} /**********************************************************************/
/* Return the informational HTTP headers about a file. */
/* Parameters: the socket to print the headers on
* the name of the file */
/**********************************************************************/
void headers(int client, const char *filename)
{
char buf[];
(void)filename; /* could use filename to determine file type */ strcpy(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), );
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), );
strcpy(buf, "\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/
/* Give a client a 404 not found status message. */
/**********************************************************************/
void not_found(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/
/* Send a regular file to the client. Use headers, and report
* errors to client if they occur.
* Parameters: a pointer to a file structure produced from the socket
* file descriptor
* the name of the file to serve */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
FILE *resource = NULL;
int numchars = ;
char buf[]; buf[] = 'A'; buf[] = '\0';
while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
{
numchars = get_line(client, buf, sizeof(buf));
} resource = fopen(filename, "r");
if (resource == NULL)
{
not_found(client);
}
else
{
// 给客户端发送头部
headers(client, filename);
// 给客户端发送内容
cat(client, resource);
}
fclose(resource);
} /**********************************************************************/
/* This function starts the process of listening for web connections
* on a specified port. If the port is 0, then dynamically allocate a
* port and modify the original port variable to reflect the actual
* port.
* Parameters: pointer to variable containing the port to connect on
* Returns: the socket */
/**********************************************************************/
int startup(u_short *port)
{
int httpd = ;
struct sockaddr_in name; httpd = socket(PF_INET, SOCK_STREAM, );
if (httpd == -)
error_die("socket");
memset(&name, , sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < )
{
error_die("bind");
} if (*port == ) /* if dynamically allocating a port */
{
int namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, (socklen_t*)&namelen) == -)
error_die("getsockname");
*port = ntohs(name.sin_port);
} if (listen(httpd, ) < )
{
error_die("listen");
}
return(httpd);
} /**********************************************************************/
/* Inform the client that the requested web method has not been
* implemented.
* Parameter: the client socket */
/**********************************************************************/
void unimplemented(int client) // 针对没有实现的方式做的回复(即除了GET和POST)
{
char buf[]; sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</TITLE></HEAD>\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/ int main(void)
{
int server_sock = -;
u_short port = ;
int client_sock = -;
struct sockaddr_in client_name;
int client_name_len = sizeof(client_name);
// pthread_t newthread; server_sock = startup(&port);
printf("httpd running on port %d\n", port); while ()
{
client_sock = accept(server_sock, (struct sockaddr *)&client_name, (socklen_t*)&client_name_len);
if (client_sock == -)
{
error_die("accept");
}
accept_request(client_sock);
/*
if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
perror("pthread_create");
*/
} close(server_sock); return();
}
源代码
这是一个非常简单的服务器,只是实现了服务器的工作原理。没有考虑其他的性能等问题,全部500行代码左右。
执行流程:
1、开启监听端口。
2、接收客户端连接。
3、解析客户端连接地址,判断是GET还是POST,或者其他,则返回错误,仅支持GET和POST。
4、判断是返回一个网页,还是调用CGI脚本,根据传过来的参数执行。
5、结束。
其中示例CGI脚本是用perl写的。功能就是根据客户端传过来的颜色值设置将要返回的网页的背景色。
其实服务器的执行流程或者说工作原理很简单,它其实就是使用socket来通信,但是通信的内容需要符合HTTP协议,即客户端--服务器交互使用的信息需要有HTTP协议头,其中协议头包含了请求的方法(GET、POST),请求的网页或者文件的路径等。
tinyhttpd服务器源码学习的更多相关文章
- 【 js 基础 】【 源码学习 】源码设计 (持续更新)
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析第二部分:undersc ...
- Redis源码学习:字符串
Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串, ...
- 【Netty源码学习】ServerBootStrap
上一篇博客[Netty源码学习]BootStrap中我们介绍了客户端使用的启动服务,接下来我们介绍一下服务端使用的启动服务. 总体来说ServerBootStrap有两个主要功能: (1)调用父类Ab ...
- 【 js 基础 】【 源码学习 】源码设计 (更新了backbone分析)
学习源码,除了学习对一些方法的更加聪明的代码实现,同时也要学习源码的设计,把握整体的架构.(推荐对源码有一定熟悉了之后,再看这篇文章) 目录结构:第一部分:zepto 设计分析 第二部分:unders ...
- mongo源码学习(四)服务入口点ServiceEntryPoint
在上一篇博客mongo源码学习(三)请求接收传输层中,稍微分析了一下TransportLayer的作用,这篇来看下ServiceEntryPoint是怎么做的. 首先ServiceEntryPoint ...
- Hadoop源码学习笔记之NameNode启动场景流程一:源码环境搭建和项目模块及NameNode结构简单介绍
最近在跟着一个大佬学习Hadoop底层源码及架构等知识点,觉得有必要记录下来这个学习过程.想到了这个废弃已久的blog账号,决定重新开始更新. 主要分以下几步来进行源码学习: 一.搭建源码阅读环境二. ...
- Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...
- Hadoop源码学习笔记(4) ——Socket到RPC调用
Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...
- SpringBoot源码学习系列之嵌入式Servlet容器
目录 1.博客前言简单介绍 2.定制servlet容器 3.变换servlet容器 4.servlet容器启动原理 SpringBoot源码学习系列之嵌入式Servlet容器启动原理 @ 1.博客前言 ...
随机推荐
- 几个PostgreSQL数据库操作总结
创建表 语法:如下 create table table_name (column_name column_type(parametes)options,…); 注意: ...
- Codeforce Round #224 Div2
一下子没打,这比赛,就被虐成狗!
- Java基础(6):foreach 方法遍历数组
foreach 并不是 Java 中的关键字,是 for 语句的特殊简化版本,在遍历数组.集合时, foreach 更简单便捷.从英文字面意思理解 foreach 也就是“ for 每一个”的意思,那 ...
- 解决HierarchyViewer不能连接真机的问题
今天想通过HierarchyViewer分析一下Android应用的布局,但是发现无法连接上真机,错误如下: 1 2 3 4 5 [hierarchyviewer]Unable to get ...
- 动画--过渡函数 transition-timing-function
transition-timing-function属性指的是过渡的“缓动函数”.主要用来指定浏览器的过渡速度,以及过渡期间的操作进展情况,其中要包括以下几种函数: (单击图片可放大) 案例展示: 在 ...
- 变形--原点 transform-origin
任何一个元素都有一个中心点,默认情况之下,其中心点是居于元素X轴和Y轴的50%处.如下图所示: 在没有重置transform-origin改变元素原点位置的情况下,CSS变形进行的旋转.位移.缩放,扭 ...
- MVC4中给TextBoxFor设置默认值和属性
例如:(特别注意在设置初始值的时候 Value 中的V要大写) @Html.TextBoxFor(model => model.CustomerCode, new { Value=" ...
- form文件上传,防止页面刷新
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>文件上传 ...
- Java高效编程之二【对所有对象都通用的方法】
对于所有对象都通用的方法,即Object类的所有非final方法(equals.hashCode.toString.clone和finalize)都有明确的通用约定,都是为了要被改写(override ...
- JavaScript的函数和事件(转)
一.默认函数 JavaScript提供了一些默认的函数 编码函数escape():将非字母.数字字符转换成ASCII码 译码函数unescape():将ASCII码转换成字母.数字字符 求值函数eva ...