server-xin我爱编程

Http 网络基础

2017-05-29  本文已影响96人  三十二蝉
image.png image.png

TCP

TCP

TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:位码即tcp标志位,有6种标示:SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)

TCP三次握手、四次挥手

image.png

HTTPS

HTTPS(Hypertext Transfer Protocol over Secure Socket Layer/基于安全套接字层的超文本传输协议,或者也可以说HTTP OVER SSL)是网景公司开发的web协议。SSL的版本最高为3.0,后来的版本被称为TLS,现在所用的协议版本一般都是TLS,但是由于SSL出现的时间比较早,所以现在一般指的SSL一般也就是TLS,本文中后面都统一使用SSL来代替TLS。HTTPS是以安全为目标的HTT通道,简单讲就是HTTP的安全版,所以你可以理解HTTPS=HTTP+SSL。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容是SSL。

数字摘要

数字摘要是采用单项Hash函数将需要加密的明文"摘要"成一串固定长度(128位)的密文,这一串密文又称为数字指纹,它有固定的长度,而且不同的明文摘要成密文,其结果总是不同的,而同样的明文摘要必定一定。“数字摘要”是https能确保数据完整性和防篡改的根本原因。常用的摘要主要有MD5、SHA1、SHA256等。

数字签名

数字签名技术就是对"非对称密钥加解密"和"数字摘要"两项技术的应用,它将摘要信息用发送者的私钥加密,与原文一起传送给接受者。接受者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原因产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输的过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。
数字签名过程:

明文——>hash运算——>摘要——>私钥加密——>数字签名

作用:

数字证书

对于请求方来说,它怎么能确定它所得到的公钥一定是从目标主机哪里发送的,
而且没有被篡改过呢?亦或者请求的目标主机本身就是从事窃取用户信息的不正当行为呢?这时候,我们需要一个权威的值得信赖的第三方机构(一般是由政府机构审核并授权的机构)来统一对外发送主机机构的公钥,只要请求方这种机构获取公钥,就避免了上述问题

有关数字签名、数字证书的图解,可以参照这篇文章

image.png image.png

HTTPS 与代理

我们知道从HTTPS的整个原理可以知道,客户端和服务器进行通信的成果,客户端是能拿到数据的,代理也一定能拿到,包括公共密钥,证书,算法等,但代理无法获取服务器的私钥,所以无法获取5/6/7/8的会话密钥,也就无法得到数据传输的明文,所以默认的情况下,charles是无法抓https的。那如何让charles转包并获取明文?也就是让charles获取私钥,获取服务器的是不可能的,那只能在通信过程中使用charles自己的证书,并在通信的过程中主动为请求的域名发放证书,流程如下


image.png

SPDY

SPDY可以说是综合了HTTPS和HTTP两者有点于一体的传输协议,主要解决:

HTTP2.0

在HTTP/1.x中,如果客户端想发起多个并行请求必须建立多个TCP连接,这无疑增大了网络开销。另外HTTP/1.x不会压缩请求和响应头,导致了不必要的网络流量,HTTP/1.x不支持资源优先级导致底层TCP连接利用率低下。而这些问题都是HTTP/2要着力解决的。HTTP2.0可以说是SPDY的升级版(其实也是基于SPDY设计的),但是HTTP2.0跟SPDY仍有不同的地方,主要有以下两点:

HTTP2 新特性

HTTP2.0性能增强的核心,全在于新增的二进制分帧层,它定义了如何封装HTTP消息并在客户端与服务器之间传输


image.png

HTTP2引入的新概念:

逻辑关系:

HTTP2.0把HTTP协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。相应地,很多流可以并行地在同一个TCP连接上交换消息。

在HTTP/1.1中,如果客户端想发送多个平行的请求以及改进性能,必须使用多个TCP连接。HTTP2.0的二进制分帧层突破了限制;客户端和服务器可以把HTTP消息分解为互不依赖的帧,然后乱序发送,最后再把另一端把它们重新组合起来。

多路复用 image.png

隧道

Web隧道(Web tunnel)是HTTP的另一种用法,可以通过HTTP应用程序访问非HTTP协议的应用程序,Web隧道允许用户允许用户通过HTTP连接发送非HTTP流量,这样就可以在HTTP上捎带其他协议数据了。使用Web隧道最常见的原因就是要在HTTP连接中嵌入非HTTP流量。这样这类流量就可以穿过只允许Web流量通过的防火墙了。

建立隧道

Web隧道是用HTTP的CONNECT方法建立起来的。CONNECT方法请求隧道网管创建一条到达任一目的服务器和端口的TCP连接,并对客户端和服务器职期间的后续数据进行盲转发。


image.png

SSL隧道

最初开发Web隧道是为了通过防火墙来传输加密的SSL流量。很多组织都会将所有流量通过分组过滤路由器和代理服务器以隧道方式传输,以提升安全性。但有些协议,比如加密SSL,其信息是加密的的,无法通过传统的代理服务器转发。隧道会通过一条HTTP连接来传输SSL流量,以穿过端口80的HTTP防火墙。


image.png

为了让SSL流量经现存的代理防火墙进行传输,HTTP中添加了一项隧道特性,在此特性中,可以将原始的加密数据放在HTTP报文中,通过普通的HTTP信道传送。


image.png

前言

平常开发一般都是用第三方库,很少自己解析Http报文。这里用Socket模拟Http报文解析。

Http请求报文一般格式

Line Content
1 请求方法+空格+URL+HTTP版本+回车+换行
2 头部字段key:val(多对) + 回车 + 换行
3 回车 + 换行
4 请求数据(POST/PUT该部分可选,GET、DELETE无该部分)

示例,Http GET请求http://image.so.com/j?q=mobile&sn=0&pn=50 地址

GET /j?q=mobile&sn=0&pn=50 HTTP/1.1
Host: image.so.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

如图,第一行为起始行,表明了以GET方式请求,子路径/j?q=mobile&sn=0&pn=50(浏览器解析) ;HTTP版本1.1。下个的每一行都是一个首部字段。浏览器发送HTTP报文的时候,会从URL中解析出域名,Host 这个Header就是域名地址。其他Header写法类似。*** 需要注意的是Content-Type: multipart/form-data 时,有一个分隔符参数boundary ***

HTTP响应报文一般格式

Line Content
1 HTTP-Version + Status-Code + Reason-Phrase
2 头部字段key:val(多对) + 回车 + 换行
3 回车 + 换行
4 响应报文内容

常见状态码:

HTTP 首部字段分类:

首部类型 作用
通用首部 既可以出现爱请求报文中,页可以出现在响应报文中
请求部首 提供更多有关请求的信息
响应部首 提供更多有关响应的信息
实体部首 描述主题的长度和内容,或者资源自身
扩展部首 HTTP 规范中没有定义的新部首

请求部首通知服务器关于客户端请求的信息,典型的有:

模拟HTTP报文解析

SimpleHttpServer类,是一个后台线程,并持有一个ServerSocket实例,用于服务端接收HTTP请求。

public class SimpleHttpServer extends Thread {
    public static final int HTTP_PORT = 8080;
    ServerSocket mSocket = null;

    public SimpleHttpServer() {
        try {
            mSocket = new ServerSocket(HTTP_PORT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (mSocket == null) {
            throw new RuntimeException("服务器Socket 初始化失败");
        }
    }

    @Override
    public void run() {
        try {
            while (true) {
                System.out.println("等待连接中");
                new DeliverThread(mSocket.accept()).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在构造函数中实例化了一个ServerSocket实例。run方法中不停接收客户端的请求。mSocket.accept()方法以阻塞的形式,等待客户端请求。一旦客户端发送请求,就实例化一个DeliverThread队请求进行处理;同时mSocket又等待另一个请求到来。

DeliverThread类,后台线程,持有一个Socket实例,对客户端请求做处理。

public class DeliverThread extends Thread {
    Socket mClientSocket;

    BufferedReader mInputStream;

    PrintStream mOutputStream;
    // 请求方法,GET POST等
    String httpMethod;
    // 子路径
    String subPath;
    // 分隔符
    String boundary;
    // 请求参数
    Map<String,String> mParams = new HashMap<>();

    Map<String,String> mHeaders = new HashMap<>();
    // 是否已经解析完Header
    boolean isParsedHeader = false;

    public DeliverThread(Socket socket){
        mClientSocket = socket;
    }

    @Override
    public void run() {
        try {
            mInputStream = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream()));
            mOutputStream = new PrintStream(mClientSocket.getOutputStream());

            parseRequest();

            handleResponse();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            IoUtils.closeQuietly(mInputStream);
            IoUtils.closeQuietly(mOutputStream);
            IoUtils.closeSocket(mClientSocket);
        }
    }

    private void parseRequest(){
        String line;
        try {
            int lineNum = 0;
            while ((line = mInputStream.readLine()) != null){
                if(lineNum ==0){
                    parseRequestLine(line);
                }

                if(isEnd(line))
                    break;

                if(lineNum != 0 && !isParsedHeader)
                    parseHeaders(line);

                if(isParsedHeader)
                    parseRequestParams(line);

                lineNum++;
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    // 是否是结束行
    private boolean isEnd(String line) {
        return line.equals("--" + boundary + "--");
    }


    private void parseRequestLine(String lineOne){
        String[] tmpStrArray = lineOne.split(" ");
        httpMethod = tmpStrArray[0];
        subPath = tmpStrArray[1];
        System.out.println("请求方式: "+httpMethod);
        System.out.println("子路径: "+subPath);
        System.out.println("HTTP版本: "+tmpStrArray[2]);
    }

    private void parseHeaders(String headerLine){
        if(headerLine.equals("")){
            isParsedHeader = true;
            System.out.println("~~~~~~~~header解析完成");
            return;
        }else if (headerLine.contains("boundary")){
            boundary = parseSecondField(headerLine);
            System.out.println("分隔符: "+ boundary);
        }else {
            parseHeaderParam(headerLine);
        }
    }

    private String parseSecondField(String line){
        String[] headerArray = line.split(";");
        parseHeaderParam(headerArray[0]);
        if(headerArray.length>1){
            return headerArray[1].split("=")[1];
        }
        return "";
    }

    private void parseHeaderParam(String headerLine){
        String[] keyvalue = headerLine.split(":");
        mHeaders.put(keyvalue[0].trim(),keyvalue[1].trim());
        System.out.println("header参数名: "+keyvalue[0].trim() + " ,参数值: "+keyvalue[1].trim());
    }

    private void parseRequestParams(String paramLine) throws IOException {
        if(paramLine.equals("--"+boundary)){
            String contentDisposition = mInputStream.readLine();
            String paramName = parseSecondField(contentDisposition);
            mInputStream.readLine();

            String paramValue = mInputStream.readLine();
            mParams.put(paramName,paramValue);
            System.out.println("param参数名: "+paramName + " , 参数值: "+paramValue);
        }
    }

    private void handleResponse(){
        sleep();

        mOutputStream.println("HTTP/1.1 200 OK");
        mOutputStream.println("Content-Type: application/json");
        mOutputStream.println();
        mOutputStream.println("{\"code\":\"success\"}");
    }

    private void sleep(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这里主要的方法有两个:parseRequest 和 handleResponse。

HttpPost类,模拟客户端发起请求:

public class HttpPost {
    public String url;
    private Map<String,String> mParamsMap = new HashMap<>();
    Socket mSocket;

    public HttpPost(String url){
        this.url = url;
    }

    public void addParam(String key,String value){
        mParamsMap.put(key,value);
    }

    public void execute(){
        try {
            mSocket = new Socket(this.url, SimpleHttpServer.HTTP_PORT);
            PrintStream outputStream = new PrintStream(mSocket.getOutputStream());
            BufferedReader inputStream = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
            final String boundary = "my_boundary_test";
            writeHeader(boundary,outputStream);
            writeParams(boundary,outputStream);
            waitResponse(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            IoUtils.closeSocket(mSocket);
        }
    }

    private void writeHeader(String boundary,PrintStream outputStream){
        outputStream.println("POST /api/login/ HTTP/1.1");
        outputStream.println("content-length:111");
        outputStream.println("Host:"+this.url+":"+ SimpleHttpServer.HTTP_PORT);
        outputStream.println("Content-Type:multipart/form-data; boundary="+boundary);
        outputStream.println("User-Agent:android");
        outputStream.println();
    }

    private void writeParams(String boundary,PrintStream outputStream){
        Iterator<String> paramsKeySet = mParamsMap.keySet().iterator();
        while (paramsKeySet.hasNext()){
            String paramName = paramsKeySet.next();
            outputStream.println("--"+boundary);
            outputStream.println("Content-Dispositin: form-data; name="+paramName);
            outputStream.println();
            outputStream.println(mParamsMap.get(paramName));
        }

        outputStream.println("--"+boundary+"--");
    }

    private void waitResponse(BufferedReader inputStream) throws IOException {
        System.out.println("请求结果:");
        String responseLine = inputStream.readLine();
        while (responseLine == null || !responseLine.contains("HTTP")){
            responseLine = inputStream.readLine();
        }

        while ((responseLine = inputStream.readLine()) != null){
            System.out.println(responseLine);
        }
    }

}

另附Utils类:

public class IoUtils {

    public static void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void closeSocket(Socket closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

先运行类ServerDemo,模拟服务端等待请求发起:

public class ServerDemo {

    public static void main(String[] arqs){
        new SimpleHttpServer().start();
    }
}

在运行类ClientDemo,模拟客户端发起请求:

public class ClientDemo {

    public static void main(String[] arqs){
        HttpPost httpPost = new HttpPost("127.0.0.1");

        httpPost.addParam("username","zj");
        httpPost.addParam("pwd","123456");

        httpPost.execute();
    }
}

运行效果:

Server解析请求 Client得到response

如果从浏览器访问127.0.0.1:8080

浏览器访问

浏览器访问127.0.0.1:8080/test

浏览器访问test子路径
上一篇 下一篇

猜你喜欢

热点阅读