美文网首页我爱编程
网络那些事(2)——基于Socket简单实现HTTP请求

网络那些事(2)——基于Socket简单实现HTTP请求

作者: GhostInMatrix | 来源:发表于2018-03-08 16:58 被阅读0次

    回顾上一节,我们介绍了Socket是啥,如何建立C/S模式下的双向通信。
    这次我们来看一看HTTP协议是什么样的,如何基于Socket建立HTTP请求。

    基于Kotlin,我推荐使用spring-boot搭建server端,方便快捷。搭建方式不是终点,附上链接有兴趣的话可以自行查看:https://projects.spring.io/spring-boot/#quick-start

    重点一:HTTP请求格式
    一次完整的HTTP请求过程,从TCP三次握手的建立成功后开始,客户端按照指定的数据格式向服务端发送请求数据(即HTTP请求),服务端接受请求后,解析这些数据,处理完成业务逻辑,最后返回一个HTTP的响应给客户端。HTTP的响应内容同样是有标准的格式。无论是什么客户端或服务端,只要遵循该HTTP规范组织数据,它一定是通用的。

    HTTP请求格式主要有四部分组成,分别是:请求行请求头空行消息体。下面我们以GET方法为例,说明每一部分的数据格式和字段意义。

    • 请求行:是请求消息的第一行,由三部分组成,分别是:请求方法(GET/POST/DELETE/PUT/HEAD)、请求资源的URI路径、HTTP的版本号。

    GET /index.html HTTP/1.1

    • 请求头:请求头中的信息有跟缓存相关的头(Cache-Control,If-Modified-Since)、客户端身份信息(User-Agent, Cookie)等。例如:

    Cache-Control: max-age=0
    Cookie:id=0x1123;stoken=fr9hfr87w7e68932%&*();ptoken=&fdospajpfejwp89@@#!
    User-Agent: Mozilla/3.0

    • 消息体:请求体是客户端发给服务端的请求数据,比如一些参数等,该部分不是必须的。
    • 空行:属于协议结构的一部分,专门用来区分请求头和消息体。在编码中的体现就是\r\n
    http request

    重点二:HTTP响应格式

    服务器接收处理完请求后会返回一个HTTP响应消息给客户端。HTTP响应消息的格式包括:状态行、响应头、空行、消息体。

    • 状态行: 位于响应消息的第一行,有HTTP协议版本号,状态码和状态说明三部分构成。

    HTTP/1.1 200 OK

    • 响应头:服务器传递给客户端用于说明服务器的一些信息,以及将来继续访问该资源时的策略。

    Connection:keep-alive
    Content-Type: application/json;charset=UTF-8
    Date: Thu, 08 Mar 2018 07:49:14 GMT
    Content-Length: 35

    • 响应体:服务端返回给客户端的数据部分,比如:视频流、图片、json字符串等。
    • 空行:专门用于区分响应头和响应体的协议,编码中同样体现为\r\n
    http response

    下面我们基于上一节所讲的Socket基础,实现简单的get请求获取数据。

    
    import android.util.Log
    import java.io.IOException
    import java.io.InputStream
    import java.io.PrintWriter
    import java.net.Socket
    import java.nio.charset.Charset
    
    class SfSocket : Runnable {
        override fun run() {
            sendSocket()
        }
        
        val HOST = "10.59.47.206"
        val PORT = 8001
        fun sendSocket() {
            val socket = Socket(HOST, PORT)
            val path = "/hello"
            val pw = PrintWriter(socket.getOutputStream())
            val input = socket.getInputStream()
            val sb = StringBuilder()
            
            /**
             * 为了成为一个合法的HTTP请求,我们需要做如下的组装,构造请求头及空行。
             */
            val request = sb.append("GET $path HTTP/1.1\r\n")
                    .append("Host: $HOST\r\n")
                    .append("Connection: Keep-Alive\r\n")
                    .append("Accept-Encoding: gzip\r\n")
                    .append("Accept: application/json\r\n")
                    .append("User-Agent: sfhttp/0.0.1\r\n")
                    .toString()  //请求头构造结束
            
            pw.write("$request\r\n")//请求头下增加空行,标志请求头到此结束。
            pw.flush()
            
            var line = ""
            var contentLength = 0
            do {
                line = readLine(input)
                //如果有Content-Length消息头时取出
                if (line.startsWith("Content-Length")) {
                    contentLength = Integer.parseInt(line.split(":")[1].trim())
                }
                //打印响应头部信息
                Log.e("sfhttp:", "Header---$line")
                //如果遇到了一个单独的回车换行(空行),则表示响应头结束。
            } while (line != "\r\n")
            
            val bodyStr = readBody(socket.getInputStream(), contentLength)
            Log.e("sfhttp:", "Body---$bodyStr")
            
            input.close()
            pw.close()
            socket.close()
        }
        
        @Throws(IOException::class)
        fun readBody(inputstream: InputStream, contentLength: Int): String {
            var byte: Byte = 0
            var list = ArrayList<Byte>()
            var total = 0
            do {
                byte = inputstream.read().toByte()
                list.add(byte)
                total++
            } while (total < contentLength)
            return String(list.toByteArray(), Charset.forName("UTF-8"))
        }
        
        
        @Throws(IOException::class)
        private fun readLine(`is`: InputStream): String {
            val lineByteList = ArrayList<Byte>()
            var readByte: Byte
            do {
                readByte = `is`.read().toByte()
                lineByteList.add(java.lang.Byte.valueOf(readByte))
            } while (readByte.toInt() != 10)
            val byteArr = lineByteList.toByteArray()
            return String(byteArr,  Charset.forName("UTF-8"))
        }
    }
    

    顺便贴一下server端的核心代码:

    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * Created by ghostinmatrix on 2018/3/5.
     */
    @RestController
    class HelloController {
        @GetMapping(value = "/hello",produces="application/json;charset=UTF-8")
        @ResponseBody
        public String hello(HttpServletResponse rsp) throws IOException {
            System.out.println("in hello");
            return "{\"url\":\"hello  from spring-boot\"}";
        }
    }
    

    日志打印出来的结果可以看出,Response 成功200,数据格式为json,数据长度为33,最后包含一个空行作为响应头的结束标志。Body内为我们根据Content-Length读出的数据。

    03-08 16:50:24.617 27716-31350/com.sf.sfhttp E/sfhttp:: Header---HTTP/1.1 200
    03-08 16:50:24.618 27716-31350/com.sf.sfhttp E/sfhttp:: Header---Content-Type: application/json;charset=UTF-8
    03-08 16:50:24.618 27716-31350/com.sf.sfhttp E/sfhttp:: Header---Content-Length: 33
    03-08 16:50:24.618 27716-31350/com.sf.sfhttp E/sfhttp:: Header---Date: Thu, 08 Mar 2018 08:50:25 GMT
    03-08 16:50:24.618 27716-31350/com.sf.sfhttp E/sfhttp:: Header---
    03-08 16:50:24.618 27716-31350/com.sf.sfhttp E/sfhttp:: Body---{"url":"hello from spring-boot"}

    总结:
    1.明确了HTTP 协议规则,空行\r\n的意义是区分请求/响应头和请求/响应体而专门设计的。
    2.试验了,只要按照上述HTTP协议格式组织请求数据,就能够作为真正的HTTP请求得到响应。
    3.说明了,市面上所存在的这些框架(Okhttp、UrlConnection、HttpClient等),其根本都是基于Socket和HTTP协议进行的封装。只不过,我们的demo非常简单,没有任何的验证措施和安全保障。但我们可以基于已有的demo继续进行封装。

    相关文章

      网友评论

        本文标题:网络那些事(2)——基于Socket简单实现HTTP请求

        本文链接:https://www.haomeiwen.com/subject/eueyfftx.html