使用C#开发HTTP服务器系列之实现Get和Post
各位朋友大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://qinyuanpei.com。在我们这个Web服务器有了一个基本的门面以后,我们是时候来用它做点实际的事情了。还记得我们最早提到HTTP协议的用途是什么吗?它叫超文本传输协议啊,所以我们必须考虑让我们的服务器能够接收到客户端传来的数据。因为我们目前完成了大部分的工作,所以对数据传输这个问题我们这里选择以最简单的GET和POST为例来实现,这样我们今天的重点就落实在Get和Post的实现这个问题上来。而从原理上来讲,无论Get方式请求还是Post方式请求,我们都可以在请求报文中获得其请求参数,不同的是前者出现在请求行中,而后者出现在消息体中。例如我们传递的两个参数num1和num2对应的数值分别是12和24,那么在具体的请求报文中我们都能找到类似“num1=12&num2=24”这样的字符结构,所以只要针对这个字符结构进行解析,就可以获得客户端传递给服务器的参数啦。
实现Get请求
首先我们来实现Get请求,Get是HTTP协议中默认的请求类型,我们平时访问网页、请求资源实际上都是通过Get方式实现的。Get方式请求需要通过类似“?id=001&option=10”这样的形式附加在URL上,因此Get方式对浏览器来说是透明的,即用户可以通过浏览器地址栏知道,这个过程中传递了哪些参数以及这些参数的值分别是什么。而由于浏览器的限制,我们通过这种方式请求的时候能够传递的参数数目和长度都是有限的,而且当参数中存在中文数值的时候还需要对其进行编码。Get方式请求相对简单,我们下面来看看它的请求报文:
GET /?num1=23&num2=12 HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
Accept-Encoding: gzip, deflate
Host: localhost:4040
Connection: Keep-Alive
Cookie: _ga=GA1.1.1181222800.1463541781
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
此时我们可以注意到在请求报文第一行,即请求行中出现了“/?num1=23&num2=12”这样的字样,这就是客户端传递给服务器的参数,我们很容易想到只需要将这个字段串中的“键”和“值”都解析出来,服务器就可以对这些数据进行处理然后返回给客户端了。所以下面我们通过这样的方式来实现,我们为HtttpRequest类增加了一个Parms属性,它是一个键和值均为字符串类型的字典,我们使用这个字典来存储和管理客户端传递来的参数。
//获取请求参数
if(this.Method == "GET" && this.URL.Contains('?'))
this.Params = GetRequestParams(lines[0].Split(' ')[1].Split('?')[1]);
- 1
- 2
- 3
- 1
- 2
- 3
显然我们首先需要判断请求类型是否为GET以及请求中是否带有参数,其方法是判断请求地址中是否含有“?”字符。这里的lines是指将报文信息按行分割以后的数组,显然请求地址在第一行,所以我们根据“?”分割该行数据以后就可以得到“num1=23&num2=12”这样的结果,这里我们使用一个方法GetRequestParms来返回参数字典,这样作做是为了复用方法,因为在处理Post请求的时候我们会继续使用这个方法。该方法定义如下:
/// <summary>
/// 从内容中解析请求参数并返回一个字典
/// </summary>
/// <param name="content">使用&连接的参数字符串</param>
/// <returns>如果存在参数则返回参数否则返回null</returns>
protected Dictionary<string, string> GetRequestParams(string content)
{
//防御编程
if(string.IsNullOrEmpty(content))
return null;
//按照&对字符进行分割
string[] reval = content.Split('&');
if(reval.Length <= 0)
return null;
//将结果添加至字典
Dictionary<string, string> dict = new Dictionary<string, string>();
foreach(string val in reval)
{
string[] kv = val.Split('=');
if(kv.Length <= 1)
dict.Add(kv[0], "");
dict.Add(kv[0],kv[1]);
}
//返回字典
return dict;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
实现Post请求
Post请求相对Get请求比较安全,因为它克服了Get请求参数长度的限制问题,而且由于它的参数是存放在消息体中的,所以在传递参数的时候对用户而言是不可见的,我们平时接触到的网站登录都是这种类型,而复杂点的网站会通过验证码、Cookie等形式来避免爬虫程序模拟登录,在Web开发中Post请求可以由一个表单发起,可以由爬虫程序如HttpWebRequest、WebClient等发起,下面我们重点来分析它的请求报文:
POST / HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586
Accept-Encoding: gzip, deflate
Host: localhost:4040
Connection: Keep-Alive
Cookie: _ga=GA1.1.1181222800.1463541781
num1=23&num2=12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
我们可以注意到此时请求行的请求方法变成了POST,而在报文结尾增加了一行内容,我们称其为“消息体”,这是一个可选的内容,请注意它前面有一个空行。所以,当我们处理一个Posst请求的时候,通过最后一行就可以解析出客户端传递过来的参数,和Get请求相同,我们这里继续使用GetRequestParams来完成解析。
if(this.Method == "POST")
this.Params = GetRequestParams(lines[lines.Length-1]);
- 1
- 2
- 1
- 2
实例
现在我们来完成一个简单地实例,服务器自然由我们这里设计的这个服务器来完成咯,而客户端则由Unity来完成因为Unity有简单的WWW可以使用。首先来编写服务端,这个继承HttpServer就好了,我们主要来写这里的方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HttpServerLib;
using System.IO;
namespace HttpServer
{
public class ExampleServer : HttpServerLib.HttpServer
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ipAddress">IP地址</param>
/// <param name="port">端口号</param>
public ExampleServer(string ipAddress, int port)
: base(ipAddress, port)
{
}
public override void OnPost(HttpRequest request)
{
//获取客户端传递的参数
int num1 = int.Parse(request.Params["num1"]);
int num2 = int.Parse(request.Params["num2"]);
//设置返回信息
string content = string.Format("这是通过Post方式返回的数据:num1={0},num2={1}",num1,num2);
//构造响应报文
HttpResponse response = new HttpResponse(content, Encoding.UTF8);
response.StatusCode = "200";
response.Content_Type = "text/html; charset=UTF-8";
response.Server = "ExampleServer";
//发送响应
ProcessResponse(request.Handler, response);
}
public override void OnGet(HttpRequest request)
{
//获取客户端传递的参数
int num1 = int.Parse(request.Params["num1"]);
int num2 = int.Parse(request.Params["num2"]);
//设置返回信息
string content = string.Format("这是通过Get方式返回的数据:num1={0},num2={1}",num1,num2);
//构造响应报文
HttpResponse response = new HttpResponse(content, Encoding.UTF8);
response.StatusCode = "200";
response.Content_Type = "text/html; charset=UTF-8";
response.Server = "ExampleServer";
//发送响应
ProcessResponse(request.Handler, response);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
因为这里需要对Get和Post进行响应,所以我们这里对OnGet和OnPost两个方法进行了重写,这里的处理方式非常简单,按照一定格式返回数据即可。下面我们来说说Unity作为客户端这边要做的工作。WWW是Unity3D中提供的一个简单的HTTP协议的封装类,它和.NET平台下的WebClient、HttpWebRequest/HttpWebResponse类似,都可以处理常见的HTTP请求如Get和Post这两种请求方式。
WWW的优势主要是简单易用和支持协程,尤其是unity3d中的协程(Coroutine)这个特性,如果能够得到良好的使用,常常能够起到事倍功半的效果。因为WWW强调的是以HTTP短链接为主的易用性,所以相应地在超时、Cookie等HTTP头部字段支持的完整性上无法和WebClient、HttpWebRequest/HttpWebRespons相提并论,当我们需要更复杂的HTTP协议支持的时候,选择在WebClient、HttpWebRequest/HttpWebResponse上进行深度定制将会是一个不错的选择。我们这里需要的是发起一个简单的HTTP请求,所以使用WWW完全可以满足我们的要求,首先我们来看在Unity3D中如何发起一个Get请求,这里给出一个简单的代码示例:
//采用GET方式请求数据
IEnumerator Get()
{
WWW www = new WWW ("http://127.0.0.1:4040/?num1=12&num2=23");
yield return www;
Debug.Log(www.text);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
现在我们是需要使用StartCoroutine调用这个方法就可以啦!同样地,对于Post请求,我们这里采用一个WWWForm来封装参数,而在网页开发中我们通常都是借助表单来向服务器传递参数的,这里给出同样简单的代码示例:
//采用POST方式请求数据
IEnumerator Post()
{
WWWForm form = new WWWForm ();
form.AddField ("num1", 12);
form.AddField ("num2", 23);
WWW www = new WWW ("http://127.0.0.1:4040/", form);
yield return www;
Debug.Log (www.text);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
而运行这个实例,我们可以得到下面的结果:
都是谁告诉你做服务器开发一定要用Java的啊,现在我们可以写出自己的服务器了,本篇结束,下期见!
使用C#开发HTTP服务器系列之实现Get和Post的更多相关文章
- 使用C#开发HTTP服务器系列之访问主页
各位朋友大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://qinyuanpei.com.在这个系列文章的第一篇中,我们着重认识和了解了HTTP协议,并在此基础上实现了一个可交互的W ...
- 使用C#开发HTTP服务器系列之Hello World
各位朋友大家好,我是秦元培,欢迎大家关注我的博客.从今天起,我将开始撰写一组关于HTTP服务器开发的系列文章.我为什么会有这样的想法呢?因为人们对Web技术存在误解,认为网站开发是Web技术的全部.其 ...
- mysql 开发进阶篇系列 55 权限与安全(安全事项 )
一. 操作系统层面安全 对于数据库来说,安全很重要,本章将从操作系统和数据库两个层面对mysql的安全问题进行了解. 1. 严格控制操作系统账号和权限 在数据库服务器上要严格控制操作系统的账号和权限, ...
- mysql 开发进阶篇系列 47 物理备份与恢复(xtrabackup 的完全备份恢复,恢复后重启失败总结)
一. 完全备份恢复说明 xtrabackup二进制文件有一个xtrabackup --copy-back选项,它将备份复制到服务器的datadir目录下.下面是通过 --target-dir 指定完全 ...
- mysql 开发进阶篇系列 46 物理备份与恢复( xtrabackup的 选项说明,增加备份用户,完全备份案例)
一. xtrabackup 选项说明 在操作xtrabackup备份与恢复之前,先看下该工具的选项,下面记录了xtrabackup二进制文件的部分命令行选项,后期把常用的选项在补上.点击查看xtrab ...
- java web开发环境配置系列(二)安装tomcat
在今天,读书有时是件“麻烦”事.它需要你付出时间,付出精力,还要付出一份心境.--仅以<java web开发环境配置系列>来祭奠那逝去的…… 1.下载tomcat压缩包,进入官网http: ...
- PHP开发环境配置系列(四)-XAMPP常用信息
PHP开发环境配置系列(四)-XAMPP常用信息 博客分类: PHP开发环境配置系列 xamppphp 完成了前面三篇后(<PHP开发环境配置系列(一)-Apache无法启动(SSL冲突)> ...
- NIO开发Http服务器(5-完结):HttpServer服务器类
最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室.我们是做WEB开发的,整天围着tomcat.n ...
- 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版
上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新 可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文 ...
随机推荐
- Leetcode 283.移动零
移动零 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 说明: 必须在原数组 ...
- POJ2421 Constructing Roads
Constructing Roads 这道题很水,就是一个裸的最小生成树,最不过把已经连接的节点的值再设为0. 代码: #include<cstdio> #include<cstri ...
- Scrapy的log日志功能
Logging Scrapy提供了log功能,可以通过 logging 模块使用 可以修改配置文件settings.py,任意位置添加下面两行 LOG_FILE = "mySpider.lo ...
- SSH日志位置
# Redhat or Fedora Core: /var/log/secure # Mandrake, FreeBSD or OpenBSD: /var/log/auth.log # SuSE: / ...
- PAT (Advanced Level) 1032. Sharing (25)
简单题,不过数据中好像存在有环的链表...... #include<iostream> #include<cstring> #include<cmath> #inc ...
- Dividing--hdu1059(动态规划)
Problem Description Marsha and Bill own a collection of marbles. They want to split the collection a ...
- 数据库中的DDL/DML/DCL解释(转)
DDL is Data Definition Language statements. Some examples:数据定义语言,用于定义和管理 SQL 数据库中的所有对象的语言 1.CREATE - ...
- C# 读自己的资源文件
Assembly assm = this.GetType().Assembly;//Assembly.LoadFrom(程序集路径); foreach (string resName in assm. ...
- Linux命令chattr和lsattr
先看字面解释: chattr:chattr - change file attributes on a Linux file system lsattr - list file attributes ...
- influxDB系列(一)
这个是github上面一个人总结的influxDB的操作手册,还不错:https://xtutu.gitbooks.io/influxdb-handbook/content/zeng.html 1. ...