基于http协议的Web服务器

论坛 期权论坛 脚本     
匿名技术用户   2020-12-23 14:08   250   0

原理:由HTTP客户端发起一个请求,建立一个到服务器指定端口(默认端口:80)的TCP连接。HTTP在此端口监听客户端发送过来的请求。一旦收到请求,服务器向客户端回复一个状态行,比如:“HTTP/1.0 200 OK”,和响应消息。

HTTP协议:

超文本传输协议,是互联网上使用的最广泛的协议,HTTP是一个客户端和服务器端请求和应答的标准(TCP)。

HTTP请求:

HTTP请求由以下部分组成

  • 请求行:请求方法,请求URL,协议版本
  • 请求头:包含若干属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
  • 空行:
  • 请求体:传递请求参数

HTTP响应:

HTTP响应由以下部分组成

  • 状态行:协议版本,状态码,状态码描述
  • 响应头部:头部字段名,字段值
  • 空行:
  • 响应正文:响应的参数

基本思想:

了解TCP协议:https://blog.csdn.net/yh971660526/article/details/82930041

  1. HTTP协议是基于TCP的通信协议(连接可靠性),实现两台主机不同进程之间的通信(客户端和服务器)。通过socket建立通信。
  2. 服务器接收客户端请求后,分析请求方法(GET(从指定的资源请求数据,参数在URL中)、POST(向指定的资源提交 要被处理的数据,参数在请求正文中)等)。
  3. 拿到请求的URL
  4. 判断资源是否存在,以网页的形式返回给客户端或返回错误。

http_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>

#define MAX 1024
#define MAIN_PAGE "index.html"
#define PAGE_404 "wwwziyuan/404.html"

static void usage(const char *proc)
{
 printf("Usage:%s prot\n",proc);
}
//监听套接字
int startup(int port)
{
 //创建socket
 int sock=socket(AF_INET,SOCK_STREAM,0);
 if(sock<0)
 {
  perror("socket");
  exit(2);
 }
 
 int opt=1;
 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

 struct sockaddr_in local;
 local.sin_family=AF_INET;
 local.sin_addr.s_addr=htonl(INADDR_ANY);
 local.sin_port=htons(port);

 //绑定
 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 {
  perror("bind");
  exit(3);
 }
 //监听
 if(listen(sock,5)<0)
 {
  perror("listen");
  exit(4);
 }

 return sock;
}
//对文本按行获取
int getLine(int sock,char line[],int len)
{
 char c='a';
 int i=0;
 while(c!='\n'&&i<len-1)
 {
  ssize_t s=recv(sock,&c,1,0);
  if(s>0){
   if(c=='\r'){
    recv(sock,&c,1,MSG_PEEK);
    if(c=='\n'){
     recv(sock,&c,1,0);
    }
    else{
     c='\n';
    }
   }
   line[i++]=c;
  }
  else
  {
   break;
  }
 }
 line[i]='\0';
 return i;
}

//错误响应
void echo_404(int sock)
{
 char line[MAX];
 struct stat st;      
 stat(PAGE_404,&st);

 sprintf(line,"HTTP/1.0 404 Not Found\r\n");
 send(sock,line,strlen(line),0);
 sprintf(line, "Content-Type:text/html;application/x-csi;application/x-jpg\r\n");
 send(sock,line,strlen(line),0);
 sprintf(line, "Content-Length:%d\r\n",st.st_size);
 send(sock,line,strlen(line),0);
 sprintf(line,"\r\n");
 send(sock,line,strlen(line),0);

 int fd=open(PAGE_404,O_RDONLY);
 sendfile(sock, fd, NULL,st.st_size);
 close(fd);
}


//响应回报文
void echoError(int sock,int status_code)
{
 switch(status_code) 
 {
  case 404:
   echo_404(sock);
   break;
  case 503: 
   break; 
  default: 
   break; 
 }
}

void clearHeader(int sock)
{
 char line[MAX];
 do{
  getLine(sock,line,sizeof(line));
 }while(strcmp(line,"\n"));
}

//GET,PATH EXIST,NO CGI QUERY_STRING IS NULL
int echo_resource(int sock,char *path,int size)
{
 char line[MAX];
 clearHeader(sock);
 int fd = open(path,O_RDONLY);
 if(fd<0)
 {
  return 500;
 }
 
 //区分后缀
 sprintf(line,"HTTP/1.0 200 OK\r\n");
 send(sock,line,strlen(line),0);
 char *p=path+strlen(path)-1;
 while(*p!='.'){
  p--;
 }
 if(strcmp(p,".css")==0)
 {
  sprintf(line,"Content-Type:text/css\r\n");
 }
 else if(strcmp(p,".js")==0)
 {
  sprintf(line,"Content-Type:application/x-javascript\r\n");
 }
 else
 {
  sprintf(line,"Content-Type:text/html;application/x-csi;application/x-jpg\r\n");
 }
 send(sock,line,strlen(line),0);

 sprintf(line,"Content-Length:%d\r\n",size);
 send(sock,line,strlen(line),0);
 sprintf(line,"\r\n");
 send(sock,line,strlen(line),0);

 sendfile(sock,fd,NULL,size);

 close(fd);
 return 200;
}

int exe_cgi(int sock,char *method,char *path,char *query_string)
{
 char line[MAX];
 int content_length=-1;

 char method_env[MAX/16];
 char query_string_env[MAX];
 char content_length_env[MAX];

 if(strcasecmp(method,"GET")==0)
 {
  clearHeader(sock);
 }else{
  do{
   getLine(sock,line,sizeof(line));
   if(strncmp(line,"Content-Length:",16)==0)
   {
    content_length=atoi(line+16);
   }
  }while(strcmp(line,"\n"));
  if(content_length==-1)
  {
   return 400;
  }
 }
 //创建管道,实现进程间通信
 int input[2];
 int output[2];

 pipe(input);
 pipe(output);

 pid_t id=fork();
 if(id<0)
 {
  return 500;
 }else if(id==0){
  //子进程
  close(input[1]);
  close(output[0]);
  
  dup2(input[0],0);
  dup2(output[1],1);

  sprintf(method_env, "METHOD=%s", method);
  putenv(method_env);
  //GET:传递query_string     POST:传递content_len
  if(strcasecmp(method, "GET") == 0)
  {
   sprintf(query_string_env, "QUERY_STRING=%s",query_string);
   putenv(query_string_env);
  }
  else
  {
   sprintf(content_length_env, "CONTENT_LENGTH=%d",content_length);
   putenv(content_length_env);}

  execl(path,path,NULL);
  exit(1);
 }else{
  //父进程
  close(input[0]);
  close(output[1]);
  
  int i=0;
  char c;
  if(strcasecmp(method,"POST")==0)
  {
   for(;i<content_length;i++)
   {
    recv(sock,&c,1,0);
    write(input[1],&c,1);
   }
  }

  sprintf(line,"HTTP/1.0 200 OK\r\n");
  send(sock,line,strlen(line),0);
  sprintf(line,"Content-Type:text/html\r\n");
  send(sock,line,strlen(line),0);
  sprintf(line,"\r\n");
  send(sock,line,strlen(line),0);

  while(read(output[0],&c,1)>0)
  {
   send(sock,&c,1,0);
  }

  waitpid(id,NULL,0);

  close(input[1]);
  close(output[0]);

 }
 return 200;
}

//处理链接请求
void *handlerRequest(void *arg)
{
 printf("get a new client\n");
 int sock=(int)arg;
 //缓冲区
 char line[MAX];
 char method[MAX/16];
 char url[MAX];
 char path[MAX];
 int i=0;
 int j=0;
 int status_code=200;
 int cgi=0;
 char *query_string = NULL;

 //按行获取
 getLine(sock,line,MAX);
 
 while(i<sizeof(method)-1 && j<sizeof(line) && !isspace(line[j]))
 {
  method[i]=line[j];
  i++,j++;
 }
 method[i] = '\0';
 //GET POST  strcasecmp忽略大小写
 if(strcasecmp(method,"GET")==0)
 {

 }else if(strcasecmp(method,"POST")==0){
  cgi=1;
 }else{
  //清理报头
  clearHeader(sock);
  status_code=400;
  goto end;
 }

 //method url http_verdion
 i=0;
 while(j<sizeof(line) && isspace(line[j]))
 {
  j++;
 }
 while(i<sizeof(url)-1 && j<sizeof(line) && !isspace(line[j]))
 {
  url[i]=line[j];
  i++,j++;
 }
 url[i]='\0';

 printf("method: %s,url: %s\n",method,url);
 
 if(strcasecmp(method,"GET")==0)
 {
  query_string=url;
  while(*query_string)
  {
   if(*query_string=='?')
   {
    *query_string='\0';
    query_string++;
    cgi=1;
    break;
   }
   query_string++;
  }
 }
 //method,url[path,query_string(GET)]
 
 sprintf(path,"wwwziyuan%s",url);

 if(path[strlen(path)-1]=='/')
 {
  strcat(path,MAIN_PAGE);
 }

 printf("method: %s,url: %s,query_string:%s\n",method,path,query_string);

 struct stat st;
 if(stat(path,&st)<0)
 {
  clearHeader(sock);
  status_code=404;
  goto end;
 }else{
  if(S_ISDIR(st.st_mode))
  {
   strcat(path,"/");
   strcat(path,MAIN_PAGE);
  }else if((st.st_mode & S_IXUSR)||\
    (st.st_mode & S_IXGRP)||\
    (st.st_mode & S_IXOTH)){
   cgi=1;
  }else{
   //do nothing
  }

  //method(GET,POST),path(EXIST),cgi(0|1),query_string(GET)
  if(cgi)
  {
   status_code=exe_cgi(sock,method,path,query_string);
  }
  else
  {
   status_code=echo_resource(sock,path,st.st_size);
  }
 }
 

end:
 if(status_code!=200)
 {
  echoError(sock,status_code);
 }

 close(sock);
}

// ./httpd  8080
int main(int argc,char *argv[])
{

 if(argc!=2)
 {
  usage(argv[0]);
  return 1;
 }
 int listen_sock=startup(atoi(argv[1]));

 for( ; ;)
 {
  struct sockaddr_in client;
  socklen_t len=sizeof(client);
  int sock=accept(listen_sock,(struct sockaddr*)&client,&len);
  if(sock<0)
  {
   perror("accept");
   continue;
  }

  //创建线程
  pthread_t tid;
  pthread_create(&tid,NULL,handlerRequest,(void *)sock);
  //分离线程,防止线程卡在这里
  pthread_detach(tid);

 }
 return 0;
}

Makefile

cc=gcc
src=http_server.c
bin=httpd

$(bin):$(src)
    $(cc) -o $@ $^ -lpthread
.PHONY:clean
clean:
       rm -f $(bin)

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP