您的位置:首页 > 理论基础 > 计算机网络

httpd源码分析

2015-08-19 13:49 375 查看
//这是一个简单的web服务器,分析它的主要原因是为搞明白,

//web服务器(Apache,IIS等)与浏览器之间的数据交互,传输

//

/* 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[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;

//从socket fd中读取一行数据。

numchars = get_line(client, buf, sizeof(buf));

//提取出前面的方法字符串,应该方法跟后面的会有空格进行分隔

//所以这时判断了是不是空格。

i = 0; j = 0;

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;

i = 0;

while (ISspace(buf[j]) && (j < sizeof(buf)))

j++;

while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))

{

url[i] = buf[j];

i++; j++;

}

url[i] = '\0';

if (strcasecmp(method, "GET") == 0)

{

query_string = url;

while ((*query_string != '?') && (*query_string != '\0'))

query_string++;

if (*query_string == '?')

{

cgi = 1;

*query_string = '\0';

query_string++;

}

}

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);

}

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 = 1;

if (!cgi)

serve_file(client, path);//这个函数能提供file信息???

else

execute_cgi(client, path, method, query_string);//这是执行cgi脚本文件???

//cgi输出直接重定向到客户端吧???

}

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];

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);

}

/**********************************************************************/

/* 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[1024];

//循环读取文件,发送给客户端。

fgets(buf, sizeof(buf), resource);

while (!feof(resource))

{

send(client, buf, strlen(buf), 0);

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[1024];

sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "Content-type: text/html\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "<P>Error prohibited CGI execution.\r\n");

send(client, buf, strlen(buf), 0);

}

/**********************************************************************/

/* 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(1);

}

/**********************************************************************/

/* 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[1024];

int cgi_output[2];

int cgi_input[2];

pid_t pid;

int status;

int i;

char c;

int numchars = 1;

int content_length = -1;

buf[0] = 'A'; buf[1] = '\0';

if (strcasecmp(method, "GET") == 0)

while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */

numchars = get_line(client, buf, sizeof(buf));

else /* POST */

{

numchars = get_line(client, buf, sizeof(buf));

while ((numchars > 0) && strcmp("\n", buf))

{

buf[15] = '\0';

if (strcasecmp(buf, "Content-Length:") == 0)

content_length = atoi(&(buf[16]));

numchars = get_line(client, buf, sizeof(buf));

}

if (content_length == -1) {

bad_request(client);

return;

}

}

//向客户端发送成功的指示。

sprintf(buf, "HTTP/1.0 200 OK\r\n");

send(client, buf, strlen(buf), 0);

if (pipe(cgi_output) < 0) {

cannot_execute(client);

return;

}

//创建一个管道

if (pipe(cgi_input) < 0) {

cannot_execute(client);

return;

}

if ( (pid = fork()) < 0 ) {

cannot_execute(client);

return;

}

if (pid == 0) /* child: CGI script */

{

char meth_env[255];

char query_env[255];

char length_env[255];

//重定向标准输出到cgi_output[1]上。

dup2(cgi_output[1], 1);

//重定向标准输入到cgi_input[0]上。

dup2(cgi_input[0], 0);

//子进程中关闭这2个fd,父进程会使用他们。

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);

}

//这里是使用exec()函数族类来执行cgi了!

//因为前面已经将stdin/stdout重定向了,所以这个进程的输出将通过管道

//送到父进程中。

//父进程与子进程之间通知管道进行连接,

//每一个都具有:写管道和读管道。

execl(path, 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);

}

}

/**********************************************************************/

/* 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 = 0;

char c = '\0';

int n;

while ((i < size - 1) && (c != '\n'))

{

//一次读取一个字符,这效率太低了吧???

//行与行之前的分隔符是\r\n.

n = recv(sock, &c, 1, 0);

/* DEBUG printf("%02X\n", c); */

if (n > 0)

{

if (c == '\r')

{

n = recv(sock, &c, 1, MSG_PEEK);

/* DEBUG printf("%02X\n", c); */

if ((n > 0) && (c == '\n'))

recv(sock, &c, 1, 0);

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 */

/**********************************************************************/

//这里是发送http头部吧

void headers(int client, const char *filename)

{

char buf[1024];

(void)filename; /* could use filename to determine file type */

strcpy(buf, "HTTP/1.0 200 OK\r\n");

send(client, buf, strlen(buf), 0);

strcpy(buf, SERVER_STRING);

send(client, buf, strlen(buf), 0);

sprintf(buf, "Content-Type: text/html\r\n");

send(client, buf, strlen(buf), 0);

strcpy(buf, "\r\n");

send(client, buf, strlen(buf), 0);

}

/**********************************************************************/

/* Give a client a 404 not found status message. */

/**********************************************************************/

void not_found(int client)

{

char buf[1024];

sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, SERVER_STRING);

send(client, buf, strlen(buf), 0);

sprintf(buf, "Content-Type: text/html\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "<BODY><P>The server could not fulfill\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "your request because the resource specified\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "is unavailable or nonexistent.\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "</BODY></HTML>\r\n");

send(client, buf, strlen(buf), 0);

}

/**********************************************************************/

/* 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 = 1;

char buf[1024];

buf[0] = 'A'; buf[1] = '\0';

while ((numchars > 0) && 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 = 0;

struct sockaddr_in name;

//基本的socket编程流程,socket->bind->listen.

httpd = socket(PF_INET, SOCK_STREAM, 0);

if (httpd == -1)

error_die("socket");

memset(&name, 0, 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)) < 0)

error_die("bind");

if (*port == 0) /* if dynamically allocating a port */

{

int namelen = sizeof(name);

if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)

error_die("getsockname");

*port = ntohs(name.sin_port);

}

if (listen(httpd, 5) < 0)

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)

{

//一次一次发送这些符合http协议的字符串,还不如一次发送的效率高呢!

char buf[1024];

sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, SERVER_STRING);

send(client, buf, strlen(buf), 0);

sprintf(buf, "Content-Type: text/html\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "</TITLE></HEAD>\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");

send(client, buf, strlen(buf), 0);

sprintf(buf, "</BODY></HTML>\r\n");

send(client, buf, strlen(buf), 0);

}

/**********************************************************************/

//分析C程序,先找main入口函数。

int main(void)

{

int server_sock = -1;

u_short port = 0;

int client_sock = -1;

struct sockaddr_in client_name;

int client_name_len = sizeof(client_name);

pthread_t newthread;

//这个函数应该做一些基本的socket流程工作吧,

//socket,bind,listen等。

server_sock = startup(&port);

printf("httpd running on port %d\n", port);

while (1)

{

//accept是阻塞的,如果有客户端来连接则返回新的fd。

client_sock = accept(server_sock,

(struct sockaddr *)&client_name,

&client_name_len);

if (client_sock == -1)

error_die("accept");

/* accept_request(client_sock); */

//新创建一个线程用于为新客户端服务,这得小心jiangshi线程,可以做pthread_detach或

//使用线程池来给线程数量做个边界最好!

if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)

perror("pthread_create");

}

close(server_sock);

return(0);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: