Android网络编程(二)--Http协议详解

关于HTTP的介绍这篇博客写的不错,我借用了里面的图,http://blog.csdn.net/itachi85/article/details/50982995

在上一篇中我们讨论了计算机网络的体系结构和各层次的作用,在我们编程中TCP或UDP都提供了socket接口进行实现,实现的例子在上一篇中,这一篇我们主要讨论一下Http协议,以及如何实现Http协议。
讨论的问题:

  1. Http协议的定义和内容。
  2. Http协议的实现。

一、Http协议的定义和内容

HTTP的英文是(HyperText Transfer Protocol),即超文本传输协议,那么什么是超文本呢,超文本就是使用超文本标记语言HTML的文本。

首先互联网上每一个资源都用一个URL所标记,URL的格式为:

http://<主机>:<端口>/<路径>

它是应用层协议,规定了数据交互的格式内容。在运输层,它采用了TCP协议保证了可靠运输。而且HTTP协议是无状态的,不过在HTTP/1.1中,添加了持续连接的功能。
下面就来看看HTTP报文的格式:
首先它有两种报文:

 - 请求报文(客户端发送给服务器的报文)
 - 响应报文(服务器返回给客户端的报文)

而且HTTP是面向文本的,所以在报文中的每一个字段都是一些ASCII码。

请求报文:

请求报文包括 请求行、请求头部、请求数据
Android网络编程(二)--Http协议详解_第1张图片
这里面第一行是请求行,举一个例子:

GET http://s2-im-notify.csdn.net/socket.io/1/xhr-polling/4-edxSXeVplQjhPcXY2V?t=1476099463098 HTTP/1.1
Host: s2-im-notify.csdn.net
Connection: keep-alive
Origin: http://blog.csdn.net
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
Accept: */*
Referer: http://blog.csdn.net/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8

请求行:
在这个请求中我们看到这个请求行为第一行,下面的都是请求头了,这个并没有请求数据。这个请求中请求方法为GET,GET是我们常用的请求方法之一,除了GET,我们还常用POST进行请求。
下面列举出请求方法,不过最常用的还是GET和POST:

  • OPTION:请求一些选项的信息
  • GET:请求读取由URL所标志的信息
  • HEAD:请求读取由URL所标志的信息的首部
  • POST:给服务器发送信息
  • PUT:在指明的URL下存储一个文档
  • DELETE:删除指明的URL所标志的资源
  • TRACE:用来进行环回测试的请求报文
  • CONNECT:用于代理服务器
    请求报头:
    请求报头有许多种,下面说一些常见的请求头:

  • Host域:指定请求的服务器地址,在HTTP/1.1中请求必须包含主机头域,否则系统会以400状态码返回。

  • Connection域:指定与连接相关的属性,比如上面例子中Connection: keep-alive,代表保持连接。
  • User-Agent域:发送请求的应用程序名称。
  • Accept-Charset域:通知服务端可以发送的编码格式。
  • Accept-Encoding域:通知服务端可以发送的数据压缩格式。
  • Accept-Language域:通知服务器可以发送的语言。
  • Accept域: 告诉WEB服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型。
  • Referer域:发送请求页面URL。浏览器向 WEB 服务器表明自己是从哪个 网页/URL 获得/点击 当前请求中的网址/URL。
  • Pramga域:主要使用 Pramga: no-cache,相当于 Cache-Control: no-cache。
  • Date域:表示消息发出的时间。
  • Cookie域:设置Cookie相关的。
  • Cache-Control域:使用的缓存机制(在请求时和响应时它的值不同,响应的下文会说到)。
    no-cache(不要缓存的实体,要求现在从服务器去取)
    max-age:(只接受 Age 值小于 max-age 值,并且没有过期的对象)
    max-stale:(可以接受过去的对象,但是过期时间必须小于 max-stale 值)
    min-fresh:(接受其新鲜生命期大于其当前 Age 跟 min-fresh 值之和的缓存对象)。

响应报文

类比请求报文,响应报文同样也包括 响应行、响应头、响应数据
Android网络编程(二)--Http协议详解_第2张图片
下面也是用一个例子来看一下:

HTTP/1.1 200 OK
Server: openresty
Date: Mon, 10 Oct 2016 12:25:25 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
Vary: Accept-Encoding
Cache-Control: private
Access-Control-Allow-Origin: *
Content-Encoding: gzip

6b0
        X[  F~G ?  $ I  @  eiWb
j }Bc{ k ]{r F+A/Z} T   U/-  [ cJ   _ _ 
     6 g s wΜs Ç #g /  sa     
O [^

*** FIDDLER: RawDisplay truncated at 128 characters. Right-click to disable truncation. ***

响应行:
第一行为响应行,对于响应行,我们可以看出状态码(也成为响应码)为200。
关于状态码,都是由三位数字组成的,分为五大类:

  • 1XX :表示通知信息,如请求收到了或者正在进行处理。
  • 2XX:表示成功,其中最常见的便是200,表示请求成功。
  • 3XX:表示重定向,如要完成请求还必须采取进一步的行动。
  • 4XX:表示客户端出错,最常见的就是404,表示未找到。
  • 5XX:表示服务器出错,比如服务器失效无法完成请求。
    除了上面的还有一些常见的响应头,比如:
    HTTP/1.1 202 Accepted //表示接受
    HTTP/1.1 400 Bad Request //表示错误的请求
    HTTP/1.1 404 Not Found //表示未找到
    HTTP/1.1 301 Moved Permanently //永久性的转移了
    响应头:
    列举一下常用的头部,(PS:在百度时发现一篇写关于这个头部和状态码非常详细的文章,我有的头部也参考的他的博客,分享给大家:http://www.2cto.com/net/201607/528141.html)
  • Age域:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间了。
  • Accept-Ranges域:WEB服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受。
  • Content-Type域:服务器通知客户端它响应的对象类型,如Content-Type:application/json。
  • Content-Range域:服务器表明该响应包含的部分对象为整个对象的哪个部分。

  • Content-Length:服务器通知响应包含对象的长度。

  • Content-Language:服务器通知客户端响应的语言。
  • Content-Encoding:服务器通知客户端响应数据的压缩格式,在上面的例子中可以看出压缩格式为gzip。
  • Connection:代表是否需要持久连接。
  • Expired:WEB服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟WEB服务器验证了其有效性后,才能用来响应客户请求。
  • Last-Modified: 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。
  • Location:服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头部指定的位置去取。
  • Proxy-Authenticate:代理服务器响应浏览器,要求其提供代理身份验证信息。
  • Server: 服务器表明自己是什么软件及版本等信息。
  • Refresh:表示浏览器应该在多少时间之后刷新文档,以秒计。
    响应数据:
    一般来说响应数据也会有一定的格式,上面的例子中因为数据用gzip压缩了,所以显示的为乱码。现在用的最火的为json格式数据,下面的例子为一个请求的响应数据,为json格式:
    {“status”:true,”error”:”“,”data”:{“id”:010101,”url”:”http://blog.csdn.net/liushuaiq/article/details/52779689“}}

二、Http协议的实现

上一篇中我们使用了java提供的socket进行了数据的传输,socket是对tcp或udp的封装,对于应用层没有实现,这一篇就对上一篇进行进一步的拓展,实现Http协议。
我们只需要在socket传输数据的基础上,进行进一步的格式化数据就可以了。
首先我封装了一个请求实体。

package com.liushuai.model;

import java.util.List;

public class Request {
    private RequestLine requestLine;
    private List requestHeaders;
    private RequestBody requestBody;

    public Request() {
        super();
    }

    public Request(RequestLine requestLine, List requestHeaders, RequestBody requestBody) {
        super();
        this.requestLine = requestLine;
        this.requestHeaders = requestHeaders;
        this.requestBody = requestBody;
    }

    public RequestLine getRequestLine() {
        return requestLine;
    }

    public void setRequestLine(RequestLine requestLine) {
        this.requestLine = requestLine;
    }

    public List getRequestHeaders() {
        return requestHeaders;
    }

    public void setRequestHeaders(List requestHeaders) {
        this.requestHeaders = requestHeaders;
    }

    public RequestBody getRequestBody() {
        return requestBody;
    }

    public void setRequestBody(RequestBody requestBody) {
        this.requestBody = requestBody;
    }

}

其中里面还进行进一步封装了请求行和请求头和请求体,代码如下:

package com.liushuai.model;

/**
 * 请求行实体
 * 
 * @author LiuShuai
 *
 */
public class RequestLine {
    /**
     * 请求方法
     */
    private String method;
    /**
     * 请求的 URL
     */
    private String url;
    /**
     * 版本
     */
    private String version;

    public RequestLine() {
        super();
    }

    public RequestLine(String method, String url, String version) {
        super();
        this.method = method;
        this.url = url;
        this.version = version;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

}
package com.liushuai.model;

/**
 * 请求头部实体
 * 
 * @author LiuShuai
 *
 */
public class RequestHeader {
    /**
     * 头部名称
     */
    private String name;
    /**
     * 头部域值
     */
    private String value;

    public RequestHeader() {
        super();
    }

    public RequestHeader(String name, String value) {
        super();
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

}
package com.liushuai.model;

/**
 * 请求体
 * 
 * @author LiuShuai
 *
 */
public class RequestBody {

    private String requestBody;

    public RequestBody() {
        super();
    }

    public RequestBody(String requestBody) {
        super();
        this.requestBody = requestBody;
    }

    public String getRequestBody() {
        return requestBody;
    }

    public void setRequestBody(String requestBody) {
        this.requestBody = requestBody;
    }
    }

下面是在socket的基础上将数据进行封装,然后格式化为Http协议中要求的格式输出:

package com.liushuai.client;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import javax.management.relation.Relation;
import javax.sql.rowset.serial.SerialArray;

import com.liushuai.model.Request;
import com.liushuai.model.RequestHeader;
import com.liushuai.model.RequestLine;
import com.liushuai.server.MyServer;

public class MyClient {
    public static void main(String[] args) {
        InetAddress inet4Address;
        try {
            inet4Address = Inet4Address.getLocalHost();
            Socket socket = new Socket("123.126.51.32", 80);
            //请求行
            RequestLine rLine = new RequestLine("GET",
                    "http://download.pinyin.sogou.com/picface/interface/cellupdate.php?h=53B24101A353034C6F75D2DB4436AA80&v=8.0.0.8381&r=0000_sogou_pinyin_win10a&ver=1.0.0.1464&cellid=80|77|79|78|2|37|41|47|52|54&cellver=2|3|2|2|4|2|4|5|3|6",
                    "HTTP/1.1");
            //请求头
            RequestHeader rh1 = new RequestHeader("User-Agent", "SogouComponentAgent");
            RequestHeader rh2 = new RequestHeader("Host", "download.pinyin.sogou.com");
            RequestHeader rh3 = new RequestHeader("Pragma", "no-cache");
            RequestHeader rh4 = new RequestHeader("Cookie",
                    "YYID=53B24101A353034C6F75D2DB4436AA80; IMEVER=8.0.0.8381; IPLOC=CN3702");
            List rhs = new ArrayList<>();
            rhs.add(rh1);
            rhs.add(rh2);
            rhs.add(rh3);
            rhs.add(rh4);
            //构造请求,这里面让请求体为空了
            Request request = new Request(rLine, rhs, null);

            // 拼装一个Http请求
            StringBuffer requestString = new StringBuffer();
            // 拼装请求行
            RequestLine reqLine = request.getRequestLine();
            StringBuffer line = new StringBuffer();
            line.append(reqLine.getMethod()).append(" ").append(reqLine.getUrl()).append(" ")
                    .append(reqLine.getVersion()).append("\r\n");
            requestString.append(line);
            // 拼装请求头部
            List requestHeaders = request.getRequestHeaders();
            StringBuffer headers = new StringBuffer();
            for (RequestHeader h : requestHeaders) {
                headers.append(h.getName()).append(":").append(h.getValue()).append("\r\n");
            }
            requestString.append(headers).append("\r\n");
            if (request.getRequestBody()!= null) {
                requestString.append(request.getRequestBody().getRequestBody());
            }
            //向服务器发送请求的数据,数据已经拼装成了Http请求报文的格式
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
            printWriter.print(requestString.toString());
            printWriter.flush();

            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            StringBuffer responseString = new StringBuffer(); 
            String servervInfo ;
            //读取服务器的响应,不过这地方会阻塞,因为Java I/O是线程阻塞的,优化一下代码应当使用Java NIO
            while((servervInfo = reader.readLine())!="\r\n"&&servervInfo!=null){
                System.out.println(servervInfo);
                responseString.append(servervInfo+"\n");
            }
            System.out.println("服务器端发送的数据为--->" + responseString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这就简单的模仿了一下Http的一次请求,我将返回结果进行了打印:

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 11 Oct 2016 12:28:29 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
X-Powered-By: PHP/7.0.8
Pragma: cache
Cache-Control: public, must-revalidate, max-age=0
Accept-Ranges: bytes
Content-Disposition: filename="SGPicFaceCellList.ini"

可以看出这次是请求成功了,返回的格式也符合Http中要求的格式。
这一篇就对HTTP协议进行了一次详细的介绍,下一篇中我准备分析一下当前最火的Android网络框架OkHttp源码中的实现。

你可能感兴趣的:(Android网络编程系列)