zoukankan      html  css  js  c++  java
  • Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求

    网页要与硬件打交道,通常的方式是使用CGI,那除了CGI之外,还有没有其他的方式呢?我们知道XHR是可以在不提交表单的情况下,实现与WEB服务端的交互的,那么服务端除了CGI来作出响应外,还有没有其他的方法呢?

    答案是有的,我们先来看效果图。

    1.XHR的POST请求效果图



    2.XHR的GET请求效果图



    因为WEB的交互在本质上就是HTTP请求,既然是HTTP请求,那么我们只要以HTTP的形式作出回应,那不就可以了吗?

    再思考一个问题,XHR请求有两种方式,一种是GET,一种是POST。这和表单的提交方式是相似的。如果有注意观察,就会发现,提交表单时采用GET请求时,表单数据会跟在URL后面,以问号作为开始,并以KEY-VALUE对的形式以&符号分隔多个KEY-VALUE对。而采用POST方法时,则不是这样的。

    那POST的数据又是如何提交出去的?服务端收到的数据又会是什么 样的呢?为此,我以C构建了一个简单的WEB服务来响应XHR的POST请求。下面是实现的步骤。

    1.在服务端以SOCKET的形式监听服务端口。

    /* server.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #define MAXLINE 1024
    #define FILE_NAME_LEN_MAX 256
    #define DEFEULT_SERVER_PORT 8000
    #define RESPONSE_HEADER_LENGTH_MAX 1024
    #define BOUNDARY_LEN_MAX 256
    #define KEY_LEN_MAX 256
    #define VALUE_LEN_MAX 1024
    #define BACK_LEN_MAX 10240
    #define FORM_DATA_LEN_MAX 10240
    
    struct FormData
    {
      char Key[KEY_LEN_MAX];
      char Value[VALUE_LEN_MAX];
      struct FormData *Next;
    };
    char * fetchMethod(const char * buf);
    int hasFormDataInUrl(const char * buf,int bufLen);
    char * fetchFileName(const char * buf,int bufLen); 
    char * readFileBytes(const char * fileName);
    char * fetchBoundary(const char * buf,int bufLen);
    void handleFormData(int connfd,const char * buf,const char * boundary,const int isFormDataInUrl/*0:not.1:is*/);
    struct FormData * fetchFormData(const char * buf,const char *boundary);
    struct FormData * fetchFormDataInUrl(const char * buf);
    void responseFormData(int connfd,struct FormData * head,const char *method);
    void response(int connfd,char *responseContent,int responseContentLength,char *responseCode,char *contentType);
    
    int main(int argc, char *argv[])
    {
    	struct sockaddr_in servaddr, cliaddr;
    	socklen_t cliaddr_len;
    	int listenfd, connfd;
    	char buf[MAXLINE];
    	char *data;
            char *responseContent;
    	char str[INET_ADDRSTRLEN];
    	char *method;
    	char *fileName;
    	char *boundary;
    	int i,n;
    	int port= DEFEULT_SERVER_PORT;
    
    	if(argc>1)
    	  {
    	    port=atoi(argv[1]);//Input port
    	  }
    	if(port<=0||port>0xFFFF)
    	  {
    	    printf("Port(%d) is out of range(1-%d)
    ",port,0xFFFF);
    	    return;
    	  }
    	listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
    	bzero(&servaddr, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	servaddr.sin_port = htons(port);
        
    	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    	listen(listenfd, 20);
    
    	printf("Listen %d
    Accepting connections ...
    ",port);
    	while (1)
    	  {
    		cliaddr_len = sizeof(cliaddr);
    		connfd = accept(listenfd, 
    				(struct sockaddr *)&cliaddr, &cliaddr_len);
    	  
    		n = read(connfd, buf, MAXLINE);
    		printf("---------------------
    ");
    		printf("received from %s at PORT %d
    ",
    		       inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
    		       ntohs(cliaddr.sin_port));
    		printf("read:%d
    %s
    ",n,buf);	
    			
    		method=fetchMethod(buf);
    		printf("method:%s
    ",method);
    		if(strcmp(method,"GET")&&strcmp(method,"POST"))		   
    	         {		 
    		  response(connfd,"Only support GET/POST",21,"200 OK","text/html");
    		  close(connfd);
    		  continue;
    		 }
    		
    		if(hasFormDataInUrl(buf,n))//Check from data in url
    		  {		 
    		    handleFormData(connfd,buf,boundary,1);//Directly response if has form data in url.(GET Method)
    		    close(connfd);
    		    continue;
    		  }
    		
    		fileName=fetchFileName(buf,n);
    		printf("Request file name:%s
    ",fileName);	
    		responseContent=readFileBytes(fileName);	
    		if(responseContent)//Response if has content.
    		  {
    		    //printf("response content:%s
    ",responseContent);
    		    response(connfd,responseContent,strlen(responseContent),"200 OK", "text/html");
    		    close(connfd);
    		    continue;
    		  }
    
    		boundary=fetchBoundary(buf,n);//If no content,web may sumbit form data.Fetch boundary firstly if has form data.
    		if((!boundary)||(strlen(boundary)<=0))//No content and no boundary,file is not exist.
    		  {		
    		    printf("Request file not exist!
    ");
    		    response(connfd,"404 Not Found",13,"200 OK","text/html");
    		    close(connfd);
    		    continue;
    		  }
    		
    		handleFormData(connfd,buf,boundary,0);//POST method.Form data is between boundaries.
    		close(connfd);
    	}
    }

    2.分析请求的方法是否为GET或POST

    char * fetchMethod(const char * buf)
    {
      int i;
      char *method;
      if(!buf)
        {
          return NULL;
        }
      method=(char *)malloc(5);
      memset(method,'',5);
      for(i=0;i<5;i++)
        {
          if(buf[i]=='/'||buf[i]==0x20/*space*/)
    	{
    	  break;
    	}
          method[i]=buf[i];     
        }
      return method;
    }


    3.依据URL的文件请求读取文件数据

    char * fetchFileName(const char * buf,int bufLen)
    {
      	char *fileName;
    	int i=0, j=-1;
    	if(!buf)
    	  {
    	    return NULL;
    	  }
    	if(bufLen<=0)
    	  {
    	    return NULL;
    	  }
    	fileName=(char *)malloc(FILE_NAME_LEN_MAX*sizeof(char));
    	memset(fileName,'',FILE_NAME_LEN_MAX);
    	//printf("
    ---------------
    ");
    	for(i=0;i<bufLen;i++)
    	  {
    	    //printf("%c",buf[i]);
    	    if(buf[i]=='/')
    	      {
    		j=0;
    		continue;
    	      }
    	    if(j<0)
    	      {
    		continue;
    	      }	
    	    if(buf[i]==0x20)
    	      {
    		break;
    	      }
    	    fileName[j]=buf[i];
    	    j++;		    
    	  }
    	//printf("
    ---------------
    ");
    	return fileName;
    }
    
    char * readFileBytes(const char * fileName)
    {
      int contentLen=0;
      char *content;
      if(!fileName)
        {
          return NULL;
        }
      
      FILE *f=fopen(fileName,"r");
      if(!f)
      {
        return NULL;
      } 
      fseek(f, 0,SEEK_END);
      contentLen=ftell(f);
      content=(char *)malloc(contentLen*sizeof(char));
      memset(content,'',contentLen);
      fseek(f,0,SEEK_SET);
      fread(content,1,contentLen,f);
      fclose(f);
      return content;
    }
    
    4.响应文件请求

    注意:最关键的就是构建HTTP的头

    void response(int connfd,char *responseContent,int responseContentLength,char *responseCode,char *contentType)
    {
      char responseHeader [RESPONSE_HEADER_LENGTH_MAX];
      int headerLen=0;
      int offset=0;
      memset(responseHeader,'',RESPONSE_HEADER_LENGTH_MAX);
    //HTTP头构建开始
      //HTTP
      strcpy(responseHeader+offset,"HTTP/1.1 ");
      offset+=strlen("HTTP/1.1 ");
      strcpy(responseHeader+offset,responseCode);
      offset+=strlen(responseCode);
      strcpy(responseHeader+offset,"
    ");
      offset+=strlen("
    ");
    
      //Server
      strcpy(responseHeader+offset, "Server: My Web Server
    ");
      offset+=strlen("Server: My Web Server
    ");
    
      //Content length
      strcpy(responseHeader+offset,"Content-Length: ");
      offset+=strlen("Content-Length: ");
      printf("content length=%d
    ",responseContentLength);
      //strcpy(responseHeader+offset,(const char*)&responseContentLength);
      sprintf(responseHeader+offset,"%d",responseContentLength);
      offset+=sizeof(int); 
      strcpy(responseHeader+offset,"
    ");
      offset+=strlen("
    ");
    
      //Connection
      strcpy(responseHeader+offset,"Connection: Keep-Alive
    ");
      offset+=strlen("Connection: Keep-Alive
    ");
    
      //Content type
      strcpy(responseHeader+offset,"Content-Type: ");
      offset+=strlen("Content-Type: ");
      strcpy(responseHeader+offset,contentType);
      offset+=strlen(contentType);
      strcpy(responseHeader+offset,"
    
    ");
      offset+=strlen("
    
    ");
      headerLen=offset;
    //HTTP头构建结束 
      //printf("Response Header:%s
    ",responseHeader);
    
      write(connfd,responseHeader,headerLen);//发送HTTP头
    
      write(connfd,responseContent,responseContentLength);//发送响应数据
    }
    5.分析XHR的FormData

    (1)XHR的POST请求:POST请求的数据是以FormData的形式发送的,在服务端会收到相应的数据,下面是具体的分析代码。

    1)分析FormData的分界线(boundary)

    char * fetchBoundary(const char * buf,int bufLen)
    {
      char *boundaryBegin;
      char *boundaryTemp;
      char *boundary;
      const char boundaryKey[]="boundary=";
      int i;
    
      if(!buf)
        {
          return NULL;
        }
      if(!strstr(buf,"multipart/form-data"))
        {
          return NULL;
        }
      boundaryBegin=strstr(buf,boundaryKey);
      if(!boundaryBegin)
        {
          return NULL;
        }
       i=0;
       //printf("###########################
    "); 
       boundaryTemp=(char *)malloc(BOUNDARY_LEN_MAX);
       memset(boundaryTemp,'',BOUNDARY_LEN_MAX);
       boundaryBegin+=strlen(boundaryKey);//move pointer.
       while(1)
         {
           boundaryTemp[i]=boundaryBegin[i];
           if(boundaryBegin[i]==0x0A)
    	 {
    	   break;
    	 }
           i++;
         }
       boundary=(char *)malloc(i);
       strcpy(boundary,boundaryTemp);
       //printf("boundary:%s
    ",boundary);
       //printf("###########################
    ");
       return boundary;
    }
    2)分析FormData的具体数据

    注:这里FormData的数据是以链表的形式储存的,FormData的结构见前面的代码。

    struct FormData * fetchFormData(const char * buf,const char *boundary)
    {
      char * begin;
      char * end;
      char * formData;
      char line[VALUE_LEN_MAX];
      char * bufTemp;
      char * temp;
      char * fromData;
      char  split[]={0x0D,0x0A};
      char keyFlag[]="Content-Disposition: form-data; name=";
      int i,j,n,boundaryLen,bufLen;
      struct FormData *head,*current,*next;
    
      if(!buf)
        {
          return;
        }
    
      if(!boundary)
        {
          return;
        }
      printf("****************Form Data**************************
    ");
    
      boundaryLen=strlen(boundary);
      begin=(char *)malloc(boundaryLen+2+1);//2 is prefix "--"
      memset(begin,'-',boundaryLen+2);//begin boundary prefix "--"
      memcpy(begin+2,boundary,boundaryLen);
    
    
      end=(char *)malloc(boundaryLen+4+1);//4 is prefix "--" and suffix "--"
      memset(end,'-',boundaryLen+4);
      memcpy(end+2,boundary,boundaryLen);
    
    
      bufLen=strlen(buf);
      bufTemp=(char *)malloc(bufLen*sizeof(char));
      memset(bufTemp,0x0,bufLen);
      memcpy(bufTemp,buf,bufLen);
    
      formData=strstr(bufTemp,begin);
    
      i=0;
      n=strlen(formData);
      memset(line,0,VALUE_LEN_MAX);
      head=(struct FormData *)malloc(sizeof(struct FormData));
      head->Next=NULL;
      current=head;
      next=NULL;
      for(i=0,j=0;i<n;i++)
        {
          if(formData[i]!=0x0A&&formData[i]!=0x0D)//Not new line.
    	{	  
    	   line[j++]=formData[i];
    	   continue;
    	}
           j=0;
          if(strlen(line)<=0)
           {	 
    	 memset(line,0,VALUE_LEN_MAX); 
    	   continue;
           }
          if(strstr(line,end))
    	{
    	  break;
    	}
          //printf("line:%s
    ",line);  
          if(*line==*begin)
    	{
    	  next=(struct FormData*)malloc(sizeof(struct FormData));	
    	  next->Next=NULL;	
    	  current->Next=next;
    	  memset(line,0,VALUE_LEN_MAX);
    	  continue;
    	}
          temp=strstr(line,keyFlag);
          if(temp)
    	{
    	  strncpy(current->Key,temp+strlen(keyFlag)+1,strlen(line)-strlen(keyFlag)-2);//kick out quote of key.
    	}
          else
    	{
    	  strcpy(current->Value,line);
    	  current=next;
    	}   
    	memset(line,0,VALUE_LEN_MAX);
        }
    
      current=head;
      while(current!=NULL)
        {
          if(strlen(current->Key)>0)
    	{
    	  printf("Name:%s  Data:%s
    ",current->Key,current->Value);
    	}
          current=current->Next;
        }
      printf("*********************************************
    ");
      return head;
    }
    

    2.XHR的GET请求:GET的请求的数据是含在URL中的,所以FormData需要从URL中分析得到。

    注:对于XHR的GET请求,如果也以FormData的形式发送(即:xhr.open("GET",url,true);xhr.send(formData);),那么在服务端没有办法直接在服务端获取到数据。原因可能是GET请求的数据是以URL的形式来发出的,但在服务端却没法收到这一数据。不过,还是有一个变通的方法。既然GET请求是以URL形式发出请求的,那我何不直接构建成像GET请求形式的URL,再以XHR的形式发送呢?结果证明是可行的,效果图见前面。

    int  hasFormDataInUrl(const char * buf,int bufLen)
    {
      int i;
      if(!buf)
        {
          return 0;
        }
      printf("===========Check form data in url===============
    ");
      for(i=0;i<bufLen;i++)
        {
          if(buf[i]==0x0D||buf[i]==0x0A)//Only check first line.
    	{
    	  break;
    	}
          if(buf[i]=='?')
    	{
    	  return 1;
    	}
        }
      printf("=================================================
    ");
      return 0;
    }
    struct FormData * fetchFormDataInUrl(const char * buf)
    {
      struct FormData *head,*current,*next;
      int i,keyIndex,valueIndex;
      if(!buf)
        {
          return NULL;
        }
      head=(struct FormData *)malloc(sizeof(struct FormData));
      head->Next=NULL;
      current=head;
      next=NULL;
      printf("****************Form Data**************************
    ");
      for(i=0,keyIndex=-1,valueIndex=-1;;i++)
        {
          if(buf[i]==0x0D||buf[i]==0x0A)//Data is in first line.
    	{
    	  break;
    	}
          if(buf[i]=='?'||buf[i]=='&')
    	{	
    	  keyIndex=0;
    	  valueIndex=-1;
    	  next=(struct FormData*)malloc(sizeof(struct FormData));	
    	  next->Next=NULL;	
    	  current->Next=next;
    	  if(buf[i]=='&')
    	    {
    	      current=next;
    	    }
    	  continue;
    	}
          if(buf[i]=='=')
    	{
    	  keyIndex=-1;
    	  valueIndex=0;
    	  continue;
    	}
          if(keyIndex>=0)
    	{
    	  current->Key[keyIndex++]=buf[i];
    	}
          if(valueIndex>=0)
    	{
    	  current->Value[valueIndex++]=buf[i];
    	}
        }
      current=head;
      while(current!=NULL)
        {
          if(strlen(current->Key)>0)
    	{
    	  printf("Name:%s  Data:%s
    ",current->Key,current->Value);
    	}
          current=current->Next;
        }
      printf("*********************************************
    ");
      return head;
    }

    6.响应XHR的请求

    注:在响应请求时,除了将收到的数据回馈回去之外,还回馈了服务端的时间。

    void responseFormData(int connfd,struct FormData * head,const char *method)
    {
      time_t current;
      struct tm *timeinfo;   
      char backData[BACK_LEN_MAX];
      char sectionFlag[]="
    ";
      int n;
      if(!head)
        {
          return;
        };
      memset(backData,0, BACK_LEN_MAX);
      sprintf(backData,"Method:%s%s",method,sectionFlag);
      n=strlen("Method")+strlen(method)+strlen(sectionFlag);
      while(head!=NULL)
        {
          if(strlen(head->Key)>0)
    	{
    	  sprintf(backData+n,"%s:%s%s",head->Key,head->Value,sectionFlag);
    	  n+=strlen(head->Key)+strlen(head->Value)+strlen(sectionFlag);
    	}
          head=head->Next;
        }
      time(&current);
      timeinfo = (struct tm *)localtime(&current);
      sprintf(backData+n,"Server time:%s%s",asctime(timeinfo),sectionFlag);
      response(connfd,backData,strlen(backData),"200 OK","text/html");
    }
    
    void handleFormData(int connfd,const char * buf,const char * boundary,const int isFormDataInUrl/*0:not.1:is*/)
    {
      struct FormData * head;
      char *method;
      if(isFormDataInUrl)
        {
          head=fetchFormDataInUrl(buf);
        }
      else
        {
          head=fetchFormData(buf,boundary);
        }
      method=fetchMethod(buf);
      responseFormData(connfd,head,method);
    }

    附上XHR的WEB实现

    xhr.html

    <html>
    <head>
        <title>Async request test</title>
        <script type="text/javascript" src="xhr.js"></script>
        <script type="text/javascript">
            function Request() {
                var url = document.getElementById("url").value;
                var data1 = document.getElementById("data1").value;
    	    			var data2 = document.getElementById("data2").value;	 
    	    			var method=document.getElementById("method").value;	 
                var formData =null;
                if(method==="POST")
                {
                	formData=new FormData();
                	formData.append("Data1", data1);
    	    				formData.append("Data2", data2);
    	    			}
    	    			else if(method==="GET")
    	    			{
    	    				url+="/?Data1="+data1+"&Data2="+data2;	
    	    			}
    	    			else
    	    			{
    	    				alert(method+" not support");		
    	    				return;
    	    			}
                Send(url, function (e) {
                    alert(e);
                },method,formData);
            }
        </script>
    </head>
    <body>
        <div>
            <a> URL:</a><input id="url" type="text" value="http://192.168.80.131:16800" style="200px;"/>
        </div>
        <div>
            <a>Data1:</a><input id="data1" type="text" value="ABCDEFG" style="200px;"/>
        </div>
        <div>
    				<a>Data2:</a><input id="data2" type="text" value="1234567" style="200px;"/>
        </div> 
        <div>
    				<a>Method:</a><input id="method" type="text" value="GET" style="200px;"/>
        </div> 
        <div>
            <input type="button" value="XHR" onclick="Request()" />
        </div>
    </body>
    </html>

    xhr.js

    function Send(url, callback,method,formData) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) //200:Success.304:Tell browser to read cache.
                {
                    if (callback === undefined || callback === null) {
                        return;
                    }
                    callback(xhr.responseText);
                }
                else {
                    alert(xhr.responseText);
                }
            }
        } 
        xhr.open(method, url, true);
        if (formData===undefined) {
            formData = null;
        }
        if(method==="GET")
        {
        	xhr.send(null);	
        }
        else
        {
        		xhr.send(formData);   
      	}
    }
    


    以上是WEB服务及响应XHR请求的简单实现,实现过程中的代码有很多需要改善的地方,请各路大牛多多指点。

    源码可以此处下载http://download.csdn.net/detail/xxdddail/6889831

    转载请注明出处http://blog.csdn.net/xxdddail/article/details/18841325



  • 相关阅读:
    haproxy 2.5 发布
    cube.js sql 支持简单说明
    基于graalvm 开发一个cube.js jdbc driver 的思路
    apache kyuubi Frontend 支持mysql 协议
    oceanbase 资源池删除说明
    基于obd 的oceanbase 扩容说明
    jfilter一个方便的spring rest 响应过滤扩展
    cube.js schema 定义多datasource 说明
    typescript 编写自定义定义文件
    meow 辅助开发cli 应用的工具
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7605043.html
Copyright © 2011-2022 走看看