C语言实现php服务器

来源:互联网 发布:霍金对人工智能看法 编辑:程序博客网 时间:2024/06/05 16:50

原理介绍

原创性申明:

本文地址是 http://blog.csdn.net/zhujunxxxxx/article/details/40658925 转载请注明出处。作者联系邮箱 zhujunxxxxx@163.com

HTTP协议的作用原理

连接:Web浏览器与Web服务器建立连接,打开一个称为socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。
请求:Web浏览器通过socket向Web服务器提交请求。HTTP的请求一般是GET或POST命令(POST用于FORM参数的传递)。GET命令的格式为:
GET 路径/文件名 HTTP/1.0
文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。

应答:Web浏览器提交请求后,通过HTTP协议传送给Web服务器。Web服务器接到后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从而在Web浏览器上显示出所请求的页面。


然后返回 网页内容到客户端,客户端更具 Conntent-Type 来解析


代码实现

其实Web服务器也就是一个一般的Socket的服务器,只不过它遵守HTTP协议来实现的罢了

//// server.c//// David J. Malan// malan@harvard.edu//// feature test macro requirements#define _GNU_SOURCE#define _XOPEN_SOURCE 700#define _XOPEN_SOURCE_EXTENDED// limits on an HTTP request's size, based on Apache's// http://httpd.apache.org/docs/2.2/mod/core.html#define LimitRequestFields 50#define LimitRequestFieldSize 4094#define LimitRequestLine 8190// number of octets for buffered reads#define OCTETS 512// header files#include <stdio.h>#include <arpa/inet.h>#include <errno.h>#include <limits.h>#include <math.h>#include <signal.h>#include <stdbool.h>#include <stdlib.h>#include <string.h>#include <strings.h>#include <unistd.h>// typestypedef char octet;// prototypesbool connected(void);bool error(unsigned short code);void handler(int signal);ssize_t load(void);const char* lookup(const char* extension);ssize_t parse(void);void reset(void);void start(short port, const char* path);void stop(void);// server's rootchar* root = NULL;// file descriptor for socketsint cfd = -1, sfd = -1;// buffer for requestoctet* request = NULL;// FILE pointer for filesFILE* file = NULL;// buffer for response-bodyoctet* body = NULL;int main(int argc, char* argv[]){// a global variable defined in errno.h that's "set by system// calls and some library functions [to a nonzero value]// in the event of an error to indicate what went wrong"errno = 0;// default to a random portint port = 0;// usageconst char* usage = "Usage: server [-p port] /path/to/root";// parse command-line argumentsint opt;while ((opt = getopt(argc, argv, "hp:")) != -1){switch (opt){// -hcase 'h':printf("%s\n", usage);return 0;// -p portcase 'p':port = atoi(optarg);break;}}// ensure port is a non-negative short and path to server's root is specifiedif (port < 0 || port > SHRT_MAX || argv[optind] == NULL || strlen(argv[optind]) == 0){// announce usageprintf("%s\n", usage);// return 2 just like bash's builtinsreturn 2;}// start serverstart(port, argv[optind]);// listen for SIGINT (aka control-c)signal(SIGINT, handler);// accept connections one at a timewhile (true){// reset server's statereset();// wait until client is connectedif (connected()){// parse client's HTTP requestssize_t octets = parse();if (octets == -1){continue;}// extract request's request-line// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.htmlconst char* haystack = request;char* needle = strstr(haystack, "\r\n");if (needle == NULL){error(400);continue;}else if (needle - haystack + 2 > LimitRequestLine){error(414);continue;}char line[needle - haystack + 2 + 1];strncpy(line, haystack, needle - haystack + 2);line[needle - haystack + 2] = '\0';// log request-lineprintf("\n%s", line);// TODO: validate request-linechar* method = line;char* request_target;char* version;char* absolute_path;char* s1;s1 = strstr (line," ");if (s1 == NULL || (version = strstr(s1 + 1, " ")) == NULL || strncmp(line + sizeof(line) - 2, "\r\n", 2) == 0){error(400);continue;}request_target = strdup(s1 + 1);request_target[version - s1 - 1] = '\0';//printf("request_target is %s, version is %s", request_target, version);if (strncmp(method,"GET",3) != 0){error(405);continue;}if (strncmp(request_target, "/", 1) != 0){error(501);continue;}if (strchr(request_target, '\"') != NULL){error(400);continue;}if (strncmp (version + 1,"HTTP/1.1",8) != 0){error(505);continue;}if (strchr(request_target, '.') == NULL){error(501);continue;}// TODO: extract query from request-targetchar *query = strchr(request_target, '?');if (query == NULL)query = "";elsequery = query + 1;//printf("query is %s\n", query);// TODO: concatenate root and absolute-pathabsolute_path = strdup(request_target);//printf("%s. %d\n", absolute_path, query-request_target);if (strcmp(query, "") != 0)absolute_path[query - request_target - 1] = '\0';//printf("absolute path is %s\n", absolute_path);char *path = (char*)malloc(1000);strcpy(path, root);//strcat(path, "/");strcat(path, absolute_path);//printf("path is %s\n", path);// TODO: ensure path existsif (access(path, F_OK) == -1) {error(404);continue;}// TODO: ensure path is readableif (access(path, R_OK) == -1) {error(403);continue;}// TODO: extract path's extensionchar* extension;if ((extension = strrchr(absolute_path, '.')) == NULL) {error(501);continue;}extension = extension + 1;//printf("extension is %s\n", extension);// dynamic contentif (strcasecmp("php", extension) == 0){// open pipe to PHP interpreterchar* format = "QUERY_STRING=\"%s\" REDIRECT_STATUS=200 SCRIPT_FILENAME=\"%s\" php-cgi";char command[strlen(format) + (strlen(path) - 2) + (strlen(query) - 2) + 1];sprintf(command, format, query, path);file = popen(command, "r");if (file == NULL){error(500);continue;}// load filessize_t size = load();if (size == -1){error(500);continue;}// subtract php-cgi's headers from body's size to get content's lengthhaystack = body;needle = memmem(haystack, size, "\r\n\r\n", 4);if (needle == NULL){error(500);continue;}size_t length = size - (needle - haystack + 4);// respond to clientif (dprintf(cfd, "HTTP/1.1 200 OK\r\n") < 0){continue;}if (dprintf(cfd, "Connection: close\r\n") < 0){continue;}if (dprintf(cfd, "Content-Length: %i\r\n", length) < 0){continue;}if (write(cfd, body, size) == -1){continue;}}// static contentelse{// look up file's MIME typeconst char* type = lookup(extension);if (type == NULL){error(501);continue;}// open filefile = fopen(path, "r");if (file == NULL){error(500);continue;}// load filessize_t length = load();if (length == -1){error(500);continue;}// TODO: respond to client// respond with Status-Linedprintf(cfd, "HTTP/1.1 200 OK\r\n");// respond with Connection headerdprintf(cfd, "Connection: close\r\n");// respond with Content-Length headerdprintf(cfd, "Content-Length: %i\r\n", length);// respond with Content-Type headerdprintf(cfd, "Content-Type: text/html\r\n");// respond with CRLFdprintf(cfd, "\r\n");write(cfd, body, length);}// announce OKprintf("\033[32m");printf("HTTP/1.1 200 OK");printf("\033[39m\n");}}}/** * Accepts a connection from a client, blocking (i.e., waiting) until one is heard. * Upon success, returns true; upon failure, returns false. */bool connected(void){struct sockaddr_in cli_addr;memset(&cli_addr, 0, sizeof(cli_addr));socklen_t cli_len = sizeof(cli_addr);cfd = accept(sfd, (struct sockaddr*) &cli_addr, &cli_len);if (cfd == -1){return false;}return true;}/** * Handles client errors (4xx) and server errors (5xx). */bool error(unsigned short code){// ensure client's socket is openif (cfd == -1){return false;}// ensure code is within rangeif (code < 400 || code > 599){return false;}// determine Status-Line's phrase// http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1const char* phrase = NULL;switch (code){case 400: phrase = "Bad Request"; break;case 403: phrase = "Forbidden"; break;case 404: phrase = "Not Found"; break;case 405: phrase = "Method Not Allowed"; break;case 413: phrase = "Request Entity Too Large"; break;case 414: phrase = "Request-URI Too Long"; break;case 418: phrase = "I'm a teapot"; break;case 500: phrase = "Internal Server Error"; break;case 501: phrase = "Not Implemented"; break;case 505: phrase = "HTTP Version Not Supported"; break;}if (phrase == NULL){return false;}// templatechar* template = "<html><head><title>%i %s</title></head><body><h1>%i %s</h1></body></html>";char content[strlen(template) + 2 * ((int) log10(code) + 1 - 2) + 2 * (strlen(phrase) - 2) + 1];int length = sprintf(content, template, code, phrase, code, phrase);// respond with Status-Lineif (dprintf(cfd, "HTTP/1.1 %i %s\r\n", code, phrase) < 0){return false;}// respond with Connection headerif (dprintf(cfd, "Connection: close\r\n") < 0){return false;}// respond with Content-Length headerif (dprintf(cfd, "Content-Length: %i\r\n", length) < 0){return false;}// respond with Content-Type headerif (dprintf(cfd, "Content-Type: text/html\r\n") < 0){return false;}// respond with CRLFif (dprintf(cfd, "\r\n") < 0){return false;}// respond with message-bodyif (write(cfd, content, length) == -1){return false;}// announce Response-Lineprintf("\033[31m");printf("HTTP/1.1 %i %s", code, phrase);printf("\033[39m\n");return true;}/** * Loads file into message-body. */ssize_t load(void){// ensure file is openif (file == NULL){return -1;}// ensure body isn't already loadedif (body != NULL){return -1;}// buffer for octetsoctet buffer[OCTETS];// read filessize_t size = 0;while (true){// try to read a buffer's worth of octetsssize_t octets = fread(buffer, sizeof(octet), OCTETS, file);// check for errorif (ferror(file) != 0){if (body != NULL){free(body);body = NULL;}return -1;}// if octets were read, append to bodyif (octets > 0){body = realloc(body, size + octets);if (body == NULL){return -1;}memcpy(body + size, buffer, octets);size += octets;}// check for EOFif (feof(file) != 0){break;}}return size;}/** * Handles signals. */void handler(int signal){// control-cif (signal == SIGINT){// ensure this isn't considered an error// (as might otherwise happen after a recent 404)errno = 0;// announce stopprintf("\033[33m");printf("Stopping server\n");printf("\033[39m");// stop serverstop();}}/** * Returns MIME type for supported extensions, else NULL. */const char* lookup(const char* extension){// TODOif (strcasecmp(extension, "css"))return "text/css";if (strcasecmp(extension, "html"))return "text/html";if (strcasecmp(extension, "gif"))return "image/gif";if (strcasecmp(extension, "ico"))return "image/x-icon";if (strcasecmp(extension, "jpg"))return "image/jpeg";if (strcasecmp(extension, "js"))return "text/javascript";if (strcasecmp(extension, "png"))return "image/png";return NULL;}/** * Parses an HTTP request. */ssize_t parse(void){// ensure client's socket is openif (cfd == -1){return -1;}// ensure request isn't already parsedif (request != NULL){return -1;}// buffer for octetsoctet buffer[OCTETS];// parse requestssize_t length = 0;while (true){// read from socketssize_t octets = read(cfd, buffer, sizeof(octet) * OCTETS);if (octets == -1){error(500);return -1;}// if octets have been read, remember new lengthif (octets > 0){request = realloc(request, length + octets);if (request == NULL){return -1;}memcpy(request + length, buffer, octets);length += octets;}// else if nothing's been read, socket's been closedelse{return -1;}// search for CRLF CRLFint offset = (length - octets < 3) ? length - octets : 3;char* haystack = request + length - octets - offset;char* needle = memmem(haystack, request + length - haystack, "\r\n\r\n", 4);if (needle != NULL){// trim to one CRLF and null-terminatelength = needle - request + 2 + 1;request = realloc(request, length);if (request == NULL){return -1;}request[length - 1] = '\0';break;}// if buffer's full and we still haven't found CRLF CRLF,// then request is too largeif (length - 1 >= LimitRequestLine + LimitRequestFields * LimitRequestFieldSize){error(413);return -1;}}return length;}/** * Resets server's state, deallocating any resources. */void reset(void){// free response's bodyif (body != NULL){free(body);body = NULL;}// close fileif (file != NULL){fclose(file);file = NULL;}// free requestif (request != NULL){free(request);request = NULL;}// close client's socketif (cfd != -1){close(cfd);cfd = -1;}}/** * Starts server. */void start(short port, const char* path){// path to server's rootroot = realpath(path, NULL);if (root == NULL){stop();}// ensure root existsif (access(root, F_OK) == -1){stop();}// ensure root is executableif (access(root, X_OK) == -1){stop();}// announce rootprintf("\033[33m");printf("Using %s for server's root, size is %ld", root, sizeof(root));printf("\033[39m\n");// create a socketsfd = socket(AF_INET, SOCK_STREAM, 0);if (sfd == -1){stop();}// allow reuse of address (to avoid "Address already in use")int optval = 1;setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));// assign name to socketstruct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(port);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);if (bind(sfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1){stop();}// listen for connectionsif (listen(sfd, SOMAXCONN) == -1){stop();}// announce port in usestruct sockaddr_in addr;socklen_t addrlen = sizeof(addr);if (getsockname(sfd, (struct sockaddr*) &addr, &addrlen) == -1){stop();}printf("\033[33m");printf("Listening on port %i", ntohs(addr.sin_port));printf("\033[39m\n");}/** * Stop server, deallocating any resources. */void stop(void){// preserve errno across this function's library callsint errsv = errno;// reset server's statereset();// free root, which was allocated by realpathif (root != NULL){free(root);}// close server socketif (sfd != -1){close(sfd);}// terminate processif (errsv == 0){// successexit(0);}else{// announce errorprintf("\033[33m");printf("%s", strerror(errsv));printf("\033[39m\n");// failureexit(1);}}


编译方法
gcc -o server server.c -lm

运行方法

./server -p 8080 public 

其中public目录是存放html和php文件的





0 0
原创粉丝点击