前言

Http我们都已经耳熟能详了,而关于Http学习的文章网上有很多,各个知识点的讲解也可说是深入浅出。然而,学习过后,我们对Http还是一知半解。问题出在了哪?

Http是一个客户机与服务器之间的通信的协议,真的想学习Http,就必须把客户机和服务器也学了,也就是说,必须立体的学习,不然我们永远都是一知半解。

现在,我们手工搭建一个服务器,立体的学习下Http,将我们以为的知识点连成线。

定义

学习前,简单的了解下定义:

Http是超文本传输协议,用于保证客户机与服务器之间的通信。在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。

  • GET - 从指定的资源请求数据。

  • POST - 向指定的资源提交要被处理的数据(向指定资源“追加/添加”数据。)

搭建Http服务器

首先我们通过HttpListener来搭建一个简易的Http服务器,代码如下:

  1. class Program
  2. {
  3. static HttpListener httpListener;
  4. static volatile bool isRun = true;
  5. static void Main(string[] args)
  6. {
  7. Listener(5180);
  8. }
  9. public static void Listener(int port)
  10. {
  11. //创建HTTP监听
  12. httpListener = new HttpListener();
  13. //监听的路径
  14. httpListener.Prefixes.Add($"http://localhost:{port}/");
  15. httpListener.Prefixes.Add($"http://127.0.0.1:{port}/");
  16. //设置匿名访问
  17. httpListener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
  18. //开始监听
  19. httpListener.Start();
  20.  
  21. while (isRun)
  22. {
  23. //等待传入的请求接受到请求时返回,它将阻塞线程,直到请求到达
  24. var context = httpListener.GetContext();
  25. //取得请求的对象
  26. HttpListenerRequest request = context.Request;
  27. Console.WriteLine($"请求模式:{request.HttpMethod}");
  28. var reader = new StreamReader(request.InputStream, Encoding.UTF8);
  29. var msgSource = reader.ReadToEnd();//读取传过来的信息+
  30. Console.WriteLine($"msgSource:{msgSource}");
  31. var msg = Uri.UnescapeDataString(msgSource);
  32. Console.WriteLine($"请求msg:{msg}");
  33. string responseString = "返回值";
  34. // 取得回应对象
  35. HttpListenerResponse response = context.Response;

  36. // 设置回应头部内容,长度,编码
  37. response.ContentEncoding = Encoding.UTF8;
  38. response.ContentType = "text/plain; charset=utf-8";

  39. response.Headers.Add("Access-Control-Allow-Origin", "*");
  40. response.Headers.Add("Cache-Control", "no-cache");

  41. byte[] buff = Encoding.UTF8.GetBytes(responseString);

  42. // 输出回应内容
  43. System.IO.Stream output = response.OutputStream;
  44. output.Write(buff, 0, buff.Length);
  45. // 必须关闭输出流
  46. output.Close();
  47. }
  48. }
  49. }

服务器搭建已经搭建完成了,现在,我们通过代码从新学习一下Http定义。

代码学习

首先我们看到,httpListener.GetContext()阻塞了线程;只有请求到达时,线程才会继续运行,请求到达时,我们将会得到一个HttpListenerRequest的请求对象。

HttpListenerRequest对象包含了请求的地址栏参数QueryString、Cookies、请求头Header等等信息。

Get请求

Get请求很简单,Get请求的数据就写在地址栏,所以我们直接可以使用HttpListenerRequest对象的QueryString来读取到,如下:

  1. HttpListenerRequest request = context.Request; //取得请求的对象
  2. Console.WriteLine($"请求模式:{request.HttpMethod}");
  3. var abc = request.QueryString["abc"];
  4. Console.WriteLine($"Get请求abc的值:{abc}");

运行Host项目,测试如下图所示:

Post请求

学习了上面的代码,我想一定有人对下面这句话感到疑惑。

  1. var reader = new StreamReader(request.InputStream, Encoding.UTF8);

为什么请求已经到了,还要去读请求中的InputStream属性呢?

我们重新看下Post的定义:向指定的资源提交要被处理的数据(向指定资源“追加/添加”数据。)。

定义太不好理解,我们翻译一下;Post的请求是先发起,一个TCP连接,然后再将数据,写入请求的InputStream属性中。

现在我们编写一个Http的Post请求,加深理解。

  1. public static void Post(string url, string param, Action<string> callback)
  2. {
  3. new Task(() =>
  4. {
  5. try
  6. {
  7. //转换输入参数的编码类型,获取bytep[]数组
  8. byte[] byteArray = Encoding.UTF8.GetBytes(param);
  9. //初始化新的webRequst
  10. //1. 创建httpWebRequest对象
  11. HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(new Uri(url));
  12. //2. 初始化HttpWebRequest对象
  13. webRequest.Method = "POST";
  14. webRequest.ContentType = "application/x-www-form-urlencoded";
  15. webRequest.ContentLength = byteArray.Length;
  16.  
  17. //3. 附加要POST给服务器的数据到HttpWebRequest对象(附加POST数据的过程比较特殊,它并没有提供一个属性给用户存取,需要写入HttpWebRequest对象提供的一个stream里面。)
  18. Stream newStream = webRequest.GetRequestStream();//创建一个Stream,赋值是写入HttpWebRequest对象提供的一个stream里面
  19. newStream.Write(byteArray, 0, byteArray.Length);
  20. newStream.Close();
  21. //4. 读取服务器的返回信息
  22. using (HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse())
  23. {
  24. using (StreamReader stream = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
  25. {
  26. string ret = stream.ReadToEnd();
  27. callback(ret);
  28. }
  29. }
  30. }
  31. catch (Exception ex)
  32. {
  33. callback("异常:" + ex.Message);
  34. }
  35. }).Start();
  36. }

可以看到,请求时,就是从指定IP地址中创建一个WebRequest对象(通过WebRequest.Create创建),然后再获取对象的请求流—GetRequestStream(),即服务端的InputStream,再向其流里写人数据。

现在我们编写一个Winform项目,测试一下Post请求,结果如下:

扩展1:Http本质上是TCP,也就是说Get请求,不去读取InputStream里的值,是被框架处理的结果呈现,如果框架处理了Get请求的InputStream,那么Get请求就也可以像Post那样,获取请求中的InputStream,然后向流里写入数据。这就是为什么有的框架Get请求也可以发送Json对象的原因。

扩展2:Post请求需要读取InputStream,也就是说,每次的Post都需要实例化一个Tcp对象去处理流,而Get请求不去读InputStream,就不用实例化Tcp了,也就是说Get请求的内存消耗更少,同理,上文提到的Get请求发送Json对象,就等于把Get请求变成了Post请求,即,大量消耗了内存,所以,如果网站需要性能好一点的话,就尽量不考虑使用这样的框架。

扩展3:在Post请求中,我们把写入InputStream的数据称为Content,而在HttpListenerRequest类的截图中,我们可以看到这三个属性ContentLength64,ContentType,ContentEncoding,他们代表着,Content的长度、类型、编码,也就是说,如果我们手写Post请求,这三个值一定要服务器解析时配置的值对上,当然,他们也都是有默认值的。通常服务器都会支持多种ContentType类型,如application/x-www-form-urlencoded或application/json,具体各种类型的数据格式,大家可以自行了解。

扩展4:MVC和WebApi都是在Http解析后执行的,也就是或,服务器先解析了Http,然后才根据请求的Url解析跳转到指定Controler和Action,然后再实例化Controler和Action时,在把相应的参数传递过去。

请求乱码

在客户端Http发起请求时,英文字母,数字会原样发送,而中文和其他字符,则直接把字符串用BASE64加密,如:%E5%95%8A%20%E4%B8%8D。这种行为,我们称之为字符串转义。

同理,在服务器端,我们需要将请求的字符串解析回来,如Uri.UnescapeDataString(msgSource)。

那为什么会有乱码?

我们会发现,乱码出现的地方都是中文和特殊字符,那么结合上文所述,我们就知道乱码出现的原因了。

两种情况,一种是框架没有做解析,或者解析失败,直接把客户端的转义后的请求发给了你;另一种是客户端和服务器的解析类型没对上,进行了错误的解析。

不过,通常情况下,服务器会替我们做好解码的工作。

跨域

上文中,我们看到在输出返回数据的时候,我们为HttpListenerResponse对象的Headers属性增加了个键值对,如下:

  1. response.Headers.Add("Access-Control-Allow-Origin", "*");

没错,这个是跨域的配置,我们在Response输出时,进行了Access-Control-Allow-Origin配置,这样,浏览器在接受到我们的返回消息时,就不会阻止它们显示了。

结语

立体的学习了Http后,我们才能更好,更快的学习Http协议,一些以前我们很难理解的解释,也可以慢慢想通了,比如Connection: keep-alive,我们现在就能更好的理解了,它就是Http请求后,不去释放Tcp对象,这样,它下一次传输数据就不用新建内存了。

----------------------------------------------------------------------------------------------------

到此HTTP的立体学习已经介绍完了,代码已经传到Github上了,欢迎大家下载。

代码已经传到Github上了,欢迎大家下载。

Github地址:https://github.com/kiba518/HttpLearning

----------------------------------------------------------------------------------------------------

注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

https://www.cnblogs.com/kiba/p/13258817.html

通过手写服务器的方式,立体学习Http的更多相关文章

  1. java24 手写服务器最终版本

    手写服务器最终版本; <?xml version="1.0" encoding="UTF-8"?> <web-app> <serv ...

  2. 手写线程池,对照学习ThreadPoolExecutor线程池实现原理!

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  3. 不使用tomcat,仅适用javaSE手写服务器--模拟登陆

    1.搭建框架 我们只是简单模拟,框架简单分三个模块 a,服务器端server包 b,servlet,根据不同的请求url,利用反射生产对应的servlet c,IO工具包,用来关闭IO流 d,编写we ...

  4. Java修炼——手写服务器项目

    项目工程总览: 1.Dispatcher类(一个请求与响应就是一个Dispatcher) package com.bjsxt.server; import java.io.IOException; i ...

  5. MyTomcat(手写服务器)

    Tomcat 是非常流行的 Web Server,它还是一个满足 Servlet 规范的容器.那么想一想,Tomcat 和我们的 Web 应用是什么关系? 从感性上来说,我们一般需要把 Web 应用打 ...

  6. 手写简易WEB服务器

    今天我们来写一个类似于Tomcat的简易服务器.可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!首先我们要准备的知识是: Socket编程 HTML HTTP协议 服务器 ...

  7. 手写Tomcat服务器

    预备知识 编写服务器用到的知识点 1) Socket 编程2) HTML3) HTTP 协议4) 反射5) XML 解析6) 服务器编写 Socket编程 https://www.cnblogs.co ...

  8. 手写Javaweb服务器

    简单web服务器 回忆socket 创建客服端(在httpClient_1包下) public class Client {    public static void main(String[] a ...

  9. 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理

    摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...

随机推荐

  1. c/c++混编

    /* head.h */#ifndef __SUM_H__ #define __SUM_H__ #ifdef __cplusplus extern "C" { #endif int ...

  2. 使用root配置的hadoop并启动会出现报错

    1.使用root配置的hadoop并启动会出现报错 错误:         Starting namenodes on [master]         ERROR: Attempting to op ...

  3. Android Studio自定义签名文件

    在项目多人开发的时候,如果使用到第三方框架,需要keystore的sha1值的时候,则需要共享debug签名才能进行程序调试 可以在gradle文件中配置如下选项,并且把keystore文件放到项目m ...

  4. 制作zipkin docker镜像

    这里使用的zipkin知识基于内存的版本,没有接入外部存储 https://zipkin.io/ https://github.com/openzipkin/zipkin https://github ...

  5. CAT12提取surface指标

    介绍 基于表面的形态学分析(VSM)的方法被越来越多的研究者使用.本文主要介绍基于SPM12和CAT12工具包进行ROI-based VSM的处理步骤. 方法 本文数据处理使用的工具是MATLAB,S ...

  6. Unit2-窝窝牌电梯

    全文共2329字,推荐阅读时间10~15分钟. 文章共分四个部分: 作业分析 评测相关 重构策略 课程体验感受 作业分析 Unit2要求我们模拟现实生活中的电梯调度情景,迭代路径是单电梯->多电 ...

  7. BUAA_OO_2020_Unit2_总结博客

    BUAA_OO_2020_Unit2_总结 2020年春季学期第八周,OO第二单元落下帷幕,三次多线程任务作罢,萌新在OO的世界里又迈出了艰难但有意义的一步,下作总结: 一.三次作业设计策略 回顾三次 ...

  8. matplotlib浅析

    首先放出matplotlib的中英文文档: 中文:https://www.matplotlib.org.cn/ 英文:https://matplotlib.org/3.1.1/index.html M ...

  9. 菜渣开源一个基于 EMIT 的 AOP 库(.NET Core)

    目录 1,快速入门 1.1 继承 ActionAttribute 特性 1.2 标记代理类型 2,如何创建代理类型 2.1 通过API直接创建 2,创建代理类型 通过API 通过 Microsoft. ...

  10. selenium(3)-针对鼠标的操作

    背景 用selenium做自动化,有时候会遇到需要模拟鼠标操作才能进行的情况,比如单击.双击.点击鼠标右键.拖拽等等. 而selenium给我们提供了一个类来处理这类事件-----------Acti ...