Skip to content

Instantly share code, notes, and snippets.

@Lhy121125
Created December 24, 2022 19:33
Show Gist options
  • Save Lhy121125/77ca5e53e95ce66d45b6135943378612 to your computer and use it in GitHub Desktop.
Save Lhy121125/77ca5e53e95ce66d45b6135943378612 to your computer and use it in GitHub Desktop.
http-server
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <linux/limits.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define MAXPENDING 5 // Maximum outstanding connection requests
#define MAX_LINE_LENGTH 1024 // Maximum line length for request and headers
#define DISK_IO_BUF_SIZE 4096 // Size of buffer for reading and sending files
static void die(const char *message)
{
perror(message);
exit(1);
}
/*
* HTTP/1.0 status codes and the corresponding reason phrases.
*/
static struct {
int status;
char *reason;
} http_status_codes[] = {
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 204, "No Content" },
{ 301, "Moved Permanently" },
{ 302, "Moved Temporarily" },
{ 304, "Not Modified" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 0, NULL } // marks the end of the list
};
static inline const char *get_reason_phrase(int status_code)
{
int i = -1;
while (http_status_codes[++i].status > 0)
if (http_status_codes[i].status == status_code)
return http_status_codes[i].reason;
return "Unknown Status Code";
}
/*
* Send HTTP status line.
*
* Returns negative if send() failed.
*/
static int send_status_line(FILE *fp, int status_code)
{
const char *reason_phrase = get_reason_phrase(status_code);
return fprintf(fp, "HTTP/1.0 %d %s\r\n", status_code, reason_phrase);
}
/*
* Send blank line.
*
* Returns number of bytes sent; returns negative if failed.
*/
static int send_blank_line(FILE *fp)
{
return fprintf(fp, "\r\n");
}
/*
* Send a generic HTTP response for error statuses (400+).
*
* Returns negative if failed.
*/
static int send_error_status(FILE *fp, int status_code)
{
if (send_status_line(fp, status_code) < 0)
return -1;
// no headers needed
if (send_blank_line(fp) < 0)
return -1;
return fprintf(fp,
"<html><body>\n"
"<h1>%d %s</h1>\n"
"</body></html>\n",
status_code, get_reason_phrase(status_code));
}
/*
* Send 301 status: redirect the browser to request_uri with '/' appended to it.
*
* Returns negative if failed.
*/
static int send301(const char *request_uri, FILE *fp)
{
if (send_status_line(fp, 301) < 0)
return -1;
// Send Location header and format redirection link in HTML in case browser
// doesn't automatically redirect.
return fprintf(fp,
"Location: %s/\r\n"
"\r\n"
"<html><body>\n"
"<h1>301 Moved Permanently</h1>\n"
"<p>The document has moved "
"<a href=\"%s/\">here</a>.</p>\n"
"</body></html>\n",
request_uri, request_uri);
}
/*
* Handle static file requests.
* Returns the HTTP status code that was sent to the browser.
*
* If send() ever fails (i.e., could not write to clnt_w), report the error and
* move on.
*/
static int handle_file_request(const char *web_root, const char *request_uri, FILE *clnt_w)
{
/*
* Define variables that we will need to use before we return.
*/
int status_code; // We'll return this value.
FILE *fp = NULL; // We'll fclose() this at the end.
// Set clnt_w to line-buffering so that lines are flushed immediately.
setlinebuf(clnt_w);
/*
* Construct the path of the requested file from web_root and request_uri.
*/
char file_path[PATH_MAX];
if (strlen(web_root) + strlen(request_uri) + 12 > sizeof(file_path)) {
// File paths can't exceed sizeof(file_path) on Linux, so just 404.
status_code = 404; // "Not Found"
if (send_error_status(clnt_w, status_code) < 0)
perror("send");
goto cleanup;
}
strcpy(file_path, web_root);
// Note: since the URI definitely begins with '/', we don't need to worry
// about appending '/' to web_root.
strcat(file_path, request_uri);
// If request_uri ends with '/', append "index.html".
if (file_path[strlen(file_path) - 1] == '/')
strcat(file_path, "index.html");
/*
* Open the requested file.
*/
// See if the requested file is a directory.
struct stat st;
if (stat(file_path, &st) == 0 && S_ISDIR(st.st_mode)) {
status_code = 301; // "Moved Permanently"
if (send301(request_uri, clnt_w) < 0)
perror("send");
goto cleanup;
}
// If unable to open the file, send "404 Not Found".
fp = fopen(file_path, "rb");
if (fp == NULL) {
status_code = 404; // "Not Found"
if (send_error_status(clnt_w, status_code) < 0)
perror("send");
goto cleanup;
}
// Otherwise, send "200 OK".
status_code = 200; // "OK"
if (send_status_line(clnt_w, status_code) < 0 || send_blank_line(clnt_w) < 0) {
perror("send");
goto cleanup;
}
/*
* Send the file.
*/
// Buffer for file contents.
char file_buf[DISK_IO_BUF_SIZE];
// Turn off buffering for clnt_w because we already have our own file_buf.
if (fflush(clnt_w) < 0) {
perror("send");
goto cleanup;
}
setbuf(clnt_w, NULL);
// Read and send file in a block at a time.
size_t n;
while ((n = fread(file_buf, 1, sizeof(file_buf), fp)) > 0) {
if (fwrite(file_buf, 1, n, clnt_w) != n) {
perror("send");
goto cleanup;
}
}
// fread() returns 0 both on EOF and on error; check if there was an error.
if (ferror(fp))
// Note that if we had an error, we sent the client a truncated (i.e.,
// corrupted) file; not much we can do about that at this point since
// we already sent the status...
perror("fread");
cleanup:
/*
* close() the FILE pointer and return.
*/
if (fp)
fclose(fp);
return status_code;
}
static int handle_mdb_request(int serv_fd2, char *request_uri, FILE *clnt_w,FILE *mdb_r){
setlinebuf(clnt_w);
const char *form =
"<html><body>"
"<h1>mdb-lookup</h1>\n"
"<p>\n"
"<form method=GET action=/mdb-lookup>\n"
"lookup: <input type=text name=key>\n"
"<input type=submit>\n"
"</form>\n"
"<p>\n";
int status_code; // We'll return this value.
char *mdb_1 = "/mdb-lookup";
// char *mdb_2 = "/mdb-lookup?";
char *mdb_3 = "/mdb-lookup?key=";
// FILE *mdb_r = fdopen(serv_fd2,"r");
// if(mdb_r == NULL) return 500;
//FILE *mdb_w = fdopen(dup(serv_fd2),"wb");
char *http = "HTTP/1.0 200 OK\r\n\r\n";
// char blank = "\r\n\r\n";
//Just lookup
if(strcmp(request_uri,mdb_1)==0){ //|| strncmp(request_uri,mdb_2,12)==0){
if(fwrite(http,strlen(http),1,clnt_w)<0) die("fwrite");
if(fwrite(form,strlen(form),1,clnt_w)<0) die("fwrite");
status_code = 200;
goto cleanup;
}
//With key
else if(strncmp(request_uri,mdb_3,strlen(mdb_3))==0){
/* status_code = 200;
if(fwrite(http,strlen(http),1,clnt_w)<0) die("fwrite");
// char *key = "";
// strcpy(key,request_uri + strlen(mdb_3));
char *key = request_uri + strlen(mdb_3);
//fprintf(stderr,"%s",key);
//if(fwrite(key,strlen(key),1,mdb_w)<0) die("fwrite: mdb");
//if(fwrite("\n",1,1,mdb_w)<0) die("fwrite: mdb");
send(serv_fd2,key,strlen(key),0);
send(serv_fd2,"\n",1,0);
if(fwrite(form,strlen(form),1,clnt_w)<0) die("fwrite");
char *border = "<p><table border>\n";
if(fwrite(border,strlen(border),1,clnt_w)<0) die("fwrite: table");
*/
char buf[4096];
int yellow = 0;
int count = 0;
// while(fgets(buf,4096,mdb_r)!= NULL){
while(1){
if(count == 0 && fgets(buf,4096,mdb_r)== NULL){
// fprintf(stderr,);
return 500;
}
if(count == 0){
status_code = 200;
if(fwrite(http,strlen(http),1,clnt_w)<0) die("fwrite");
// char *key = "";
// strcpy(key,request_uri + strlen(mdb_3));
char *key = request_uri + strlen(mdb_3);
//fprintf(stderr,"%s",key);
//if(fwrite(key,strlen(key),1,mdb_w)<0) die("fwrite: mdb");
//if(fwrite("\n",1,1,mdb_w)<0) die("fwrite: mdb");
send(serv_fd2,key,strlen(key),0);
send(serv_fd2,"\n",1,0);
if(fwrite(form,strlen(form),1,clnt_w)<0) die("fwrite");
char *border = "<p><table border>\n";
if(fwrite(border,strlen(border),1,clnt_w)<0) die("fwrite: table");
// char buf[4096];
// int yellow = 0;
}
//stop the loop!!!!
if(buf[0]=='\n')break;
//setup the color....
if(yellow == 0){
char *td = "<tr><td>\n";
if(fwrite(td,strlen(td),1,clnt_w)<0) die("fwrite:white");
yellow = 1;
}
else{
char *td = "<tr><td bgcolor=yellow>\n";
if(fwrite(td,strlen(td),1,clnt_w)<0) die("fwrite:yellow");
yellow = 0;
}
fwrite(buf,strlen(buf),1,clnt_w);
}
char *end = "</table>\n</body></html>\r\n\r\n";
fwrite(end,strlen(end),1,clnt_w);
goto cleanup;
}
cleanup:
//fclose(mdb_r);
//fclose(mdb_w);
return status_code;
}
void handle_client(const char *web_root, int clnt_fd, const char *clnt_ip, int serv_fd2,FILE *mdb_r )
{
/*
* Open client file descriptor as FILE pointers.
*/
FILE *clnt_r = fdopen(clnt_fd, "rb");
if (clnt_r == NULL)
die("fdopen");
FILE *clnt_w = fdopen(dup(clnt_fd), "wb");
if (clnt_w == NULL)
die("fdopen");
/*
* Let's parse the request line.
*/
// Note: we'll use these fields at the end when we log the connection.
int status_code;
char *method = NULL, *request_uri = NULL, *http_version = NULL, *extra;
char request_buf[MAX_LINE_LENGTH];
if (fgets(request_buf, sizeof(request_buf), clnt_r) == NULL) {
// Socket closed prematurely; there isn't much we can do
status_code = 400; // "Bad Request"
goto terminate_connection;
}
char *token_separators = "\t \r\n"; // tab, space, new line
method = strtok(request_buf, token_separators);
request_uri = strtok(NULL, token_separators);
http_version = strtok(NULL, token_separators);
extra = strtok(NULL, token_separators);
// Note: We must not modify request_buf past this point, because method,
// request_uri, http_version, and extra point to within request_buf.
// Check that we have exactly three tokens in the request line.
if (!method || !request_uri || !http_version || extra) {
status_code = 501; // "Not Implemented"
send_error_status(clnt_w, status_code);
goto terminate_connection;
}
// We only support GET requests.
if (strcmp(method, "GET")) {
status_code = 501; // "Not Implemented"
send_error_status(clnt_w, status_code);
goto terminate_connection;
}
// We only support HTTP/1.0 and HTTP/1.1.
if (strcmp(http_version, "HTTP/1.0") && strcmp(http_version, "HTTP/1.1")) {
status_code = 501; // "Not Implemented"
send_error_status(clnt_w, status_code);
goto terminate_connection;
}
// request_uri must begin with "/".
if (!request_uri || *request_uri != '/') {
status_code = 400; // "Bad Request"
send_error_status(clnt_w, status_code);
goto terminate_connection;
}
// Ensure request_uri does not contain "/../" and does not end with "/..".
int uri_len = strlen(request_uri);
if (uri_len >= 3) {
char *tail = request_uri + (uri_len - 3);
if (strcmp(tail, "/..") == 0 || strstr(request_uri, "/../") != NULL) {
status_code = 400; // "Bad Request"
send_error_status(clnt_w, status_code);
goto terminate_connection;
}
}
/*
* Skip HTTP headers.
*/
// We need another buffer for trashing the headers, because request_buf
// still currently holds the method, request_uri, and http_version strings.
char line_buf[MAX_LINE_LENGTH];
while (1) {
if (fgets(line_buf, sizeof(line_buf), clnt_r) == NULL) {
// Socket closed prematurely; there isn't much we can do
status_code = 400; // "Bad Request"
goto terminate_connection;
}
// Check if we have reached the end of the headers, i.e., an empty line.
if (strcmp("\r\n", line_buf) == 0 || strcmp("\n", line_buf) == 0)
break;
}
/*
* We have a well-formed HTTP GET request; time to handle it.
*/
//Now we need to consider handle mdb-lookup
char *mdb_1 = "/mdb-lookup";
char *mdb_2 = "/mdb-lookup?";
if(strcmp(request_uri,mdb_1)==0 || strncmp(request_uri,mdb_2,12)==0){
status_code = handle_mdb_request(serv_fd2,request_uri,clnt_w,mdb_r);
if(status_code == 500){
//fflush(clnt_w);
send_error_status(clnt_w,status_code);
goto terminate_connection;
}
//goto terminate_connection;
}else{
status_code = handle_file_request(web_root, request_uri, clnt_w);
//goto terminate_connection;
}
terminate_connection:
/*
* Done with client request; close the connection and log the transaction.
*/
// Closing can FILE pointers can also produce errors, which we log.
if (fclose(clnt_w) < 0)
perror("send");
if (fclose(clnt_r) < 0)
perror("recv");
fprintf(stderr, "%s \"%s %s %s\" %d %s\n",
clnt_ip,
method,
request_uri,
http_version,
status_code,
get_reason_phrase(status_code));
}
int main(int argc, char *argv[])
{
/*
* Configure signal-handling.
*/
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
// Ignore SIGPIPE so that we don't terminate when we call
// send() on a disconnected socket.
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL))
die("sigaction(SIGPIPE)");
/*
* Parse arguments.
*/
if (argc != 5) {
fprintf(stderr, "usage: %s <server-port> <web-root> <mdb-host> <mdb-port>\n", argv[0]);
exit(1);
}
char *serv_port = argv[1];
char *web_root = argv[2];
char *mdb_host = argv[3];
char *mdb_port = argv[4];
/*
* Construct server socket to listen on serv_port.
*/
struct addrinfo hints, *info;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // Only accept IPv4 addresses
hints.ai_socktype = SOCK_STREAM; // stream socket for TCP connections
hints.ai_protocol = IPPROTO_TCP; // TCP protocol
hints.ai_flags = AI_PASSIVE; // Construct socket address for bind()ing
int addr_err;
if ((addr_err = getaddrinfo(NULL, serv_port, &hints, &info)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(addr_err));
exit(1);
}
int serv_fd = socket(info->ai_family, info->ai_socktype, info->ai_protocol);
if (serv_fd < 0)
die("socket");
if (bind(serv_fd, info->ai_addr, info->ai_addrlen) < 0)
die("bind");
if (listen(serv_fd, 8) < 0)
die("listen");
freeaddrinfo(info);
struct addrinfo hints2, *info2;
memset(&hints2,0,sizeof(hints2));
hints2.ai_family = AF_INET;
hints2.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
int addr_err2;
if((addr_err2 = getaddrinfo(mdb_host,mdb_port,&hints2,&info2))){
fprintf(stderr,"getaddrinfo: %s\n", gai_strerror(addr_err2));
exit(1);
}
// Create socket() according to the address family, socket type, and
// protocol of the address info.
int serv_fd2 = socket(info2->ai_family, info2->ai_socktype, info2->ai_protocol);
if (serv_fd2 < 0)
die("socket");
// Connect socket with server address; the IP address and port in
// info->ai_addr should be the same address and port that getaddrinfo()
// parsed from server_address and server_port.
if (connect(serv_fd2, info2->ai_addr, info2->ai_addrlen) < 0)
die("connect");
freeaddrinfo(info2);
FILE *mdb_r = fdopen(serv_fd2,"r");
if(mdb_r == NULL) die("fdopen");
/*
* Server accept() loop.
*/
for (;;) {
// We only need sockaddr_in since we only accept IPv4 peers.
struct sockaddr_in clnt_addr;
socklen_t clnt_len = sizeof(clnt_addr);
int clnt_fd = accept(serv_fd, (struct sockaddr *)&clnt_addr, &clnt_len);
if (clnt_fd < 0)
die("accept");
char clnt_ip[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &clnt_addr.sin_addr, clnt_ip, sizeof(clnt_ip))
== NULL)
die("inet_ntop");
handle_client(web_root, clnt_fd, clnt_ip,serv_fd2,mdb_r);
}
/*
* UNREACHABLE
*/
close(serv_fd);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment