二、基于Socket创建一个Http请求

本篇文章是okhttp介绍的第二篇,如要复习http理论基础知识,参:一、http 理论基础
本篇主要是okhttp主要部分源码与逻辑。

基于socket创建一个http

首先通过上篇的文章,我们了解了tcp与http协议,而在java中,如果我们要创建一个tcp连接,则可以使用Socket(套接字),socket 是java为我们创建与建立tcp连接而提供的一套api,注:(创建udp则是DatagramSocket)。那么这里称socket连接就是tcp连接,下同。建立socket连接后我们通过输出流发送指定http数据格式,就能创建一个http请求了,然后获取输入流,读取数据,获取响应结果。

package com.wzh.server;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class HelloWorld extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String queryStr = req.getQueryString();

        resp.setContentType("text/html");
        resp.setHeader("head", "value");

        //设置状态码
        resp.setStatus(200);
        PrintWriter out = resp.getWriter();
        out.println("Hello world, this message is from servlet get!");
        out.println("query:" + queryStr);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();//获取cookie
        String protocol = req.getProtocol();//协议版本
        Enumeration<String> headerNames = req.getHeaderNames();//header
        String query = req.getQueryString();//拼接在url后的请求参数

        resp.setContentType("text/html;charset=utf-8");//设置响应内容类型
        resp.setStatus(200);

        resp.addHeader("respHead", "v1");//添加头
        resp.addCookie(new Cookie("cookie1", "value1"));//添加cookie

        PrintWriter writer = resp.getWriter();
        writer.println("protocol :" + protocol);

        writer.println("your cookie start-------");
        if (cookies != null)
            for (Cookie c : cookies)
                writer.println(c.getName() + "=" + c.getValue());
        writer.println("cookie end-------");

        writer.println("your header start-------");
        if (headerNames != null)
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                writer.println(key + ":" + req.getHeader(key));
            }
        writer.println("your header end-------");
        writer.println("query:" + query);

        BufferedReader reader = req.getReader();//获取body
        String body = "";
        String s;
        while ((s = reader.readLine()) != null) {
            body += s + "\r\n";
        }
        reader.close();
        writer.println("body:" + body);
        writer.close();
    }

}

这是一个简单的servlet,搭配个Tomcat,服务启动,浏览器能访问就是正常了。这里重点不是服务器,而是客户端,http服务器可以是其它任何服务器。主要有doGet,doPost两个方法,当http请求进来时,会进相应的方法进行处理,这里说下doPost函数。可以看到主要是把客户端传上来的cookie,header,与body信息都返回给客户端,代码比较简单,应该能看懂,不懂请查jsp 相关文章。接下来看客户端发送一个get请求

public class SocketTest {
    public static final String IP = "192.168.31.236";
    public static final int PORT = 8080;

    public static void main(String[] args) throws IOException {
        httpGetTest();
//        httpPostTest();
//        httpPostOkioTest();
    }

    private static void httpGetTest() throws IOException {
        Socket socket = new Socket(IP, PORT);
        socket.setKeepAlive(true);
        socket.setSoTimeout(100 * 1000);
        OutputStream os = socket.getOutputStream();

        String protocol = "GET /WebServerTest_war_exploded/HelloWorld?a=b&c=d HTTP/1.1\r\n";//必须
        os.write(protocol.getBytes());
        String header = "Host: " + IP + ":" + PORT + "\r\n" +//必须
                "ContentType:application/x-www-form-urlencoded\r\n" +
                "Content-Length: 0\r\n" +
                "Connection: Keep-Alive\r\n" +
                "head1:Value1\r\n";

        os.write(header.getBytes());
        os.write("\r\n".getBytes());

        InputStream is = socket.getInputStream();
        String resp = readInputStream(is);
        System.out.println(resp);
        socket.close();
    }
    
    private static String readInputStream(InputStream is) throws IOException {
        byte[] bytes = new byte[1024];
        int i;
        String str = "";
        while ((i = is.read(bytes)) != -1) {
            str += new String(bytes, 0, i);
        }
        return str;
    }
    }

使用ip,端口创建socket,设置超时时间,获取 输出流,然后先write了 请求行(Request Line),就是GET /WebServerTest_war_exploded/HelloWorld?a=b&c=d HTTP/1.1\r\n这一行,按 请求方法 GET空格请求地址 /WebServerTest_war_exploded/HelloWorld?a=b&c=d空格 协议版本号 HTTP/1.1,以\r\n换行符结束。然后write 请求头(Request Header),head中以key:空格value的形式,换行符分隔,其中Host 头是必须添加的,最后write一个换行符结束发送,然后获取输入流,读取数据,打印后如下:

HTTP/1.1 200 
head: value
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 61
Date: Tue, 23 Jul 2019 16:35:58 GMT

Hello world, this message is from servlet get!
query:a=b&c=d

这就是服务器返回的所有数据,第一行称为 响应行 包含服务器协议与 状态码,然后是 响应head ,在服务器中添加了一个自定义head,其它head为服务器底层自动添加,然后 head 空行之后就是 响应body 也就是我们常拿数据的地方,这是返回一句话,并把客户端传的请求参数返回 回来。这里没有对服务器返回解析为具体的code,head,body,其实就是字符串处理了,按照指定规则解析数据就行了。接下来看Post.

    private static void httpPostTest() throws IOException {
        Socket socket = new Socket(IP, PORT);
        OutputStream os = socket.getOutputStream();

        String protocol = "POST /WebServerTest_war_exploded/HelloWorld HTTP/1.1\r\n";//必须
        os.write(protocol.getBytes());

        String body = "key1=value1&key2=value2";

        String head = "Host: " + IP + ":" + PORT + "\r\n" +//必须
                "Content-Length: " + body.length() + "\r\n" +//必须
                "Content-Type: application/x-www-form-urlencoded\r\n" +
                "Connection: Keep-Alive\r\n" +
                "User-Agent: myhttp/3\r\n" +
                "Cookie: key=value; key2=value2\r\n" +
                "head1: Value1\r\n" +
                "head2: Value2\r\n";

        os.write(head.getBytes());
        os.write("\r\n".getBytes());
        os.write(body.getBytes());
        os.flush();

        InputStream is = socket.getInputStream();
        String resp = readInputStream(is);
        System.out.println("" + resp);
        is.close();

        socket.close();
    }

可以看到,除请求行与host必须外,如果body里有数据,Content-length也是必须的,长度就是body数据长。然后cookie也是放在head中的,以; 分隔,key=value形式。在write完head头 后,再写一个换行符,然后写body。返回结果如下:

HTTP/1.1 200 
respHead: v1
Set-Cookie: cookie1=value1
Content-Type: text/html;charset=utf-8
Content-Length: 362
Date: Tue, 23 Jul 2019 17:00:48 GMT

protocol :HTTP/1.1
your cookie start-------
key=value
key2=value2
cookie end-------
your header start-------
host:192.168.31.236:8080
content-length:23
content-type:application/x-www-form-urlencoded
connection:Keep-Alive
user-agent:myhttp/3
cookie:key=value; key2=value2
head1:Value1
head2:Value2
your header end-------
query:null
body:key1=value1&key2=value2

对应于服务器post方法,新增了一个cookie 与 respHead给客户端,空行之后全是body。至此,我们自己手动通过socket实现一个http就算完成了,主要是head与body中的换行与规则要清楚,可以自己试试。这部分可以说是okhttp最核心的部分,如果你能看懂上面这些,并有大致理解,okhttp你就看懂了一半,因为okhttp也是通过socket建立连接,然后发送相应规则的数据,然后解析成相应的对象供上层去处理。上一篇文章也说了,只要你能建立一个Tcp连接,然后发送相应格式的数据,就是发起一个http请求,如Telnet工具,这个工具可以建立一个tcp连接。

wzhdeMacBook-Pro:shell wzh$ telnet 10.10.13.201 8080
Trying 10.10.13.201...
Connected to 10.10.13.201.
Escape character is '^]'.
^]
telnet> 
POST /WebServerTest_war_exploded/HelloWorld HTTP/1.1
head1: headValue1
head2: headValue2
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
Host: 10.10.14.235:8080
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.10.0

key1=value1&key2=value2
HTTP/1.1 200 
respHead: v1
Set-Cookie: cookie1=value1
Content-Type: text/html;charset=utf-8
Content-Length: 342
Date: Mon, 15 Jul 2019 03:42:28 GMT

protocol :HTTP/1.1
your cookie start-------
cookie end-------
your header start-------
head1:headValue1
head2:headValue2
content-type:application/x-www-form-urlencoded
content-length:23
host:10.10.14.235:8080
connection:Keep-Alive
accept-encoding:gzip
user-agent:okhttp/3.10.0
your header end-------
query:null
body:key1=value1&key2=value2

Telnet 工具的详细用法请参考其它文章。

Okio 用法

okio是由square公司开发的用于IO读取。补充了Java.io和java.nio的不足,以便能够更加方便,快速的访问、存储和高效处理数据。内部的读写操作是在内存中进行的。github地址主要方法
在这里插入图片描述
其中Source对应于输入流,Sink对应于输出流,下为主要类对应关系

	Sink 		-->    OutPutStream  输出流
	Source 		-->	   InputStream 输入流

	BufferedSink   --> BufferedOutputStream  缓存输出流
	BufferedSource --> BufferedInputStream  缓存输入流

每个source与sink的构造都可以通过File、Socket、InputStream来创建,用法差不多,下面为例子

public class OkIoTest {
    public static void main(String[] args) {
        try {
            Source fileSource = Okio.source(new File("/Users/xx.tx"));
            BufferedSource buffSource = Okio.buffer(fileSource);

            File f2 = new File("/Users/newFile.txt");
            BufferedSink bufferSink = Okio.buffer(Okio.sink(f2));
            //文件复制
            String line;
            while ((line=buffSource.readUtf8Line())!=null) {
                System.out.println(line);
                bufferSink.writeUtf8(line);
            }
            buffSource.close();
            bufferSink.close();

            //保存对象到文件
            bufferSink = Okio.buffer(Okio.sink(f2));

            Person p = new Person(10, "aa");
            bufferSink.writeString(serialize(p).base64(), Charset.defaultCharset());
            bufferSink.close();

            //从文件读取对象
            String string=Okio.buffer(Okio.source(f2)).readString(Charset.defaultCharset());
            ByteString byteString = ByteString.decodeBase64(string);
            Buffer b = new Buffer();
            b.write(byteString);
            Person p2 = (Person) new ObjectInputStream(b.inputStream()).readObject();
            System.out.println("p2=" + p2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static ByteString serialize(Object o) throws IOException {
        Buffer buffer = new Buffer();
        try (ObjectOutputStream objectOut = new ObjectOutputStream(buffer.outputStream())) {
            objectOut.writeObject(o);
        }
        return buffer.readByteString();
    }
}

展示了从一个文件输入到别一个文件输入的例子,和对象的存取,用法还是比较简单的。大家只需要记住对应关系类就可以了。下面展示用okio发送post请求

private static void httpPostOkioTest() throws IOException {
        Socket socket = new Socket(IP, PORT);
        socket.setKeepAlive(true);
        BufferedSink sink = Okio.buffer(Okio.sink(socket.getOutputStream()));

        String protocol = "POST /WebServerTest_war_exploded/HelloWorld HTTP/1.1\r\n";//必须
        sink.writeUtf8(protocol);

        String body = "key1=value1&key2=value2";

        String head = "Host: " + IP + ":" + PORT + "\r\n" +//必须
                "Content-Length: " + body.length() + "\r\n" +//必须
                "Content-Type: application/x-www-form-urlencoded\r\n" +
                "Connection: Keep-Alive\r\n" +
                "Accept-Encoding: gzip\r\n" +
                "User-Agent: myhttp/3\r\n" +
                "Cookie: key=value; key2=value2\r\n" +
                "head1: Value1\r\n" +
                "head2: Value2\r\n";

        sink.writeUtf8(head);
        sink.writeUtf8("\r\n");
        sink.writeUtf8(body);
        sink.flush();

        BufferedSource is = Okio.buffer(Okio.source(socket.getInputStream()));
        String resp = is.readByteString().utf8();
        System.out.println(resp);

        socket.close();
    }

输出和httpPostTest 是一样的。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以回答您的问题。以下是一个基于Sockethttp请求监听程序的实现示例: ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class HttpServer { public static void main(String[] args) { int port = 8080; // 监听的端口号 try { ServerSocket server = new ServerSocket(port); System.out.println("Server started on port " + port); while (true) { Socket client = server.accept(); System.out.println("New client connected"); // 读取客户端发送的请求 BufferedReader requestReader = new BufferedReader(new InputStreamReader(client.getInputStream())); String requestLine = requestReader.readLine(); System.out.println("Request line: " + requestLine); // 关闭连接 client.close(); } } catch (IOException e) { e.printStackTrace(); } } } ``` 这个程序会监听本地的8080端口,当客户端连接时,它会读取客户端发送的http请求的第一行(即请求行),并将其打印到控制台上。当请求处理完毕后,程序会关闭连接,等待下一个客户端连接。 当您需要在Java应用程序中监听端口时,只需创建一个ServerSocket对象并调用其accept()方法,该方法将阻塞程序直到有客户端连接。一旦客户端连接,您就可以读取客户端发送的数据并对其进行处理。处理完成后,关闭连接并等待下一个客户端连接。 希望这个示例能够帮助到您!如果您还有其他问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值