• 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



  • 相关阅读:
    电子书下载:ASP .NET 4 高级程序设计.第4版
    电子书下载:Construct Game Development Beginner's Guide
    电子书下载:Game Programming Gems 2
    电子书下载:C# 5.0 in a Nutshell 5th
    电子书下载:MySQL Stored Procedure Programming
    Windows 8各个版本的功能详解与对比【中文详表】
    电子书下载:Silverlight 5 in Action
    电子书下载:Metro Revealed Building Windows 8 apps with XAML and C#.
    电子书下载:Microsoft Visual C# 2010 Step by Step
    电子书下载:Teach Yourself Microsoft Dynamics CRM 2011 in 24 Hours
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7605043.html
Copyright © 2020-2023  润新知