對HTTP1.1協議有基本的瞭解後,就可以用C語言來實現一個極簡的http server了。HTTP1.1 協議基礎可以看這篇文章 HTTP 基礎: 請求和響應的消息交互細節。
這裏實現的極簡 http server 主要完成此功能: 通過URL訪問server根目錄的html類型文件,server目錄默認就是httpd可執行文件的所在目錄。這個簡單的demo的目的是爲了加深對http交互的理解,代碼如下:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <ctype.h>
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define LOG(fmt, ...) printf(fmt" %s:%d\n", ##__VA_ARGS__, __FILENAME__, __LINE__)
#define EXIT(error) do {perror(error); exit(EXIT_FAILURE);} while(0)
#define MAX_REQUEST_LEN 10240
#define MAX_METHOD_LEN 32
#define MAX_URI_LEN 256
int parse_request(int sockfd, char *method, char *uri) {
char buff[MAX_REQUEST_LEN] = {0};
ssize_t len = recv(sockfd, buff, sizeof(buff), 0);
if (len <= 0) {
LOG("call recv error, ret %d", (int)len);
return -1;
}
char *cur = buff;
int i = 0;
while (i < MAX_METHOD_LEN && !isspace(*cur)) {
method[i++] = *cur++;
}
method[i] = '\0';
while(isspace(*cur)) cur++;
i = 0;
while (i < MAX_URI_LEN && !isspace(*cur)) {
uri[i++] = *cur++;
}
uri[i] = '\0';
return 0;
}
void unimplemented(int client) {
char buff[] =
"HTTP/1.0 501 Method Not Implemented\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"Method Not Implemented";
send(client, buff, sizeof(buff), 0);
}
void not_found(int client)
{
char buff[] =
"HTTP/1.0 404 NOT FOUND\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"The resource specified is unavailable.\r\n";
send(client, buff, strlen(buff), 0);
}
void url_decode(const char *src, char *dest) {
const char *p = src;
char code[3] = {0};
while (*p && *p != '?') {
if(*p == '%') {
memcpy(code, ++p, 2);
*dest++ = (char)strtoul(code, NULL, 16);
p += 2;
} else {
*dest++ = *p++;
}
}
*dest = '\0';
}
void do_get(int sockfd, const char *uri) {
char filename[MAX_URI_LEN] = {0};
const char *cur = uri + 1;
size_t len = strlen(cur);
if (len == 0) {
strcpy(filename, "index.html");
} else {
url_decode(cur, filename);
}
printf("%s\n", filename);
FILE *f = fopen(filename, "r");
if (NULL == f) {
not_found(sockfd);
return;
}
char header[] =
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n";
send(sockfd, header, sizeof(header), 0);
char line[128] = {0};
while (fgets(line, sizeof(line), f) != NULL) {
send(sockfd, line, strlen(line), 0);
memset(line, 0, sizeof(line));
}
char end[] = "\r\n";
send(sockfd, end, 2, 0);
fclose(f);
}
void *process(void* psockfd) {
int sockfd = *(int*)psockfd;
char method[MAX_METHOD_LEN] = {0};
char uri[MAX_URI_LEN] = {0};
if (parse_request(sockfd, method, uri) != 0)
goto FINAL;
if (strcmp(method, "GET") == 0) {
do_get(sockfd, uri);
} else {
unimplemented(sockfd);
}
FINAL:
close(sockfd);
return NULL;
}
int create_server_fd (unsigned int port) {
int serverfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverfd == -1)
EXIT("create socket fail");
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = INADDR_ANY;
if (bind(serverfd,(struct sockaddr *)&server, sizeof(server)) == -1)
EXIT("bind fail");
if (listen(serverfd, 10) == -1)
EXIT("listen fail");
return serverfd;
}
int main() {
int serverfd, connfd;
pthread_t tid;
struct sockaddr_in client;
socklen_t clientlen = sizeof(client);
unsigned int port = 5000;
serverfd = create_server_fd(port);
LOG("Server started, listen port %d", port);
while (1) {
connfd = accept(serverfd, (struct sockaddr *)&client, &clientlen);
if (pthread_create(&tid, NULL, process, &connfd) == 0) {
unsigned char *ip = (unsigned char*)&client.sin_addr.s_addr;
unsigned short port = client.sin_port;
LOG("request %u.%u.%u.%u:%5u", ip[0], ip[1], ip[2], ip[3], port);
} else {
EXIT("create thread fail");
}
}
return 0;
}
這個http server demo採用的併發模型是多線程,編譯的時候記得鏈接-lpthread
。