Socket简单实现Get和Post请求

1、背景

最近看了OkHttp源码,用的责任链模式,通过一些列拦截器(如重定向、缓存、网络链接等)实现网络请求,服务器连接部分是通过Socket,因此手动实现一下,加深记忆。

2、HTTP请求格式

在这里插入图片描述
贴一张图,来自HTTP请求格式
这个图就是实现的核心,通过Socket的输出流,把上述格式的字符串数据传给服务器,再通过输入流读取服务器的响应即可。

3、具体实现

3、1 封装一下请求
/**
 * Description: http请求封装
 * Author : pxq
 * Date : 19-12-28 下午5:34
 */
public class Request {
	//路径对应的URL
    private URL url;
	//原本请求的路径
    private String path;
	//请求协议
    private String protocol;
	//请求端口
    private int port;
	//请求主机
    private String host;
    //默认get请求
    private Method method = Method.GET;
	//额外的请求头
    private Map<String, String> headers;
    //额外的请求参数
    private Map<String, String> params;

    public Request(String url){
        init(url);
    }

    private void init(String path) {
        try {
            this.path = path;
            this.url = new URL(path);
            this.port = getDefaultPort(this.url);
            this.host = this.url.getHost();
            this.protocol = this.url.getProtocol();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    private static int getDefaultPort(URL url){
        if (url.getPort() != -1){
            return url.getPort();
        }
        //https
        if ("https".equals(url.getProtocol())){
            return 443;
        }
        //http
        return 80;
    }
    .......getter setter省略.......
}

当传入一个api路径时,比如http://www.xxxxx.com/ 可以通过URL去解析这个路径拿到它的协议、端口、host等信息。需要注意的是当路径里没有显示的指明端口时,会返回-1,这里做了简单处理:http默认端口80和https默认端口443。

3、2 实现Socket请求

首先创建Socket,https用SSLSocketFactory创建,http直接new Socket()即可,OkHttp做了Socket重用的,这里没做。

/**
 * Description: 一个socket池,可以做socket缓存重用
 * Author : pxq
 * Date : 19-12-28 下午5:49
 */
public class SocketPool {


    /**
     * 获取socket
     * @param request
     * @return
     * @throws IOException
     */
    public Socket getSocket(Request request) throws IOException {
        if ("https".equals(request.getProtocol())){
            return SSLSocketFactory.getDefault().createSocket(request.getHost(), request.getPort());
        }
        return new Socket(request.getHost(), request.getPort());
    }

}

创建之后,就已经完成了与服务器的三次握手,接下来只要按http请求格式完成数据读写即可。需要注意的是,有些api需要 User-Agent这个请求头,有些不需要,而且这个请求头还不能乱写…

/**
 * Description: 同步请求
 * Author : pxq
 * Date : 19-12-28 下午5:52
 */
public class Call {

    private HttpClient mHttpClient;

    private Request mRequest;

    public Call(HttpClient client, Request request) {
        mRequest = request;
        mHttpClient = client;
    }

    public Response call() throws IOException {

        String httpPath = mRequest.getUrl().getPath();
        String query = handleParams();
        if (mRequest.getMethod() == Method.GET) {
            if (query.length() != 0) {
                httpPath += "?" + query;
            }
        }
        StringBuilder httpData = new StringBuilder();
        getHttpLine(httpData, httpPath);
        getHttpHeaders(httpData);
        getHttpBody(httpData, query);

        //执行网络请求
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        Socket socket = null;
        try {
            long start = System.currentTimeMillis();
            socket = mHttpClient.getSocketPool().getSocket(mRequest);
            System.out.println(System.currentTimeMillis() - start);
            OutputStream outputStream = socket.getOutputStream();
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            //把请求数据传给服务器
            String requestData = httpData.toString();
            System.out.println(requestData);
            /*
            * GET http://gank.io/api/data/Android/10/1 HTTP/1.1
              User-Agent:sockettest/1.1
              Host:gank.io
             */
            System.out.println("request data end..");
            bufferedWriter.write(requestData);
            bufferedWriter.flush();
            //获取服务器返回的数据
            StringBuilder response = new StringBuilder();
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String temp;
            while ((temp = bufferedReader.readLine()) != null) {
                response.append(temp);
                response.append("\n");
            }
            return new Response(response.toString());
        } finally {
            if (bufferedWriter != null) {
                bufferedWriter.close();
            }
            if (bufferedReader != null) {
                bufferedReader.close();
            }
            if (socket != null) {
                socket.close();
            }
        }
    }

    /**
     * 获取请求行
     *
     * @param httpRequest 入参
     */
    private void getHttpLine(StringBuilder httpRequest, String path) {
        //line
        httpRequest.append(mRequest.getMethod().value());
        httpRequest.append(" ");
        //拼接完整的请求路径
        httpRequest.append(mRequest.getProtocol());
        httpRequest.append("://");
        //host
        httpRequest.append(mRequest.getHost());
        if (path.length() == 0){
            path = "/";
        }
        httpRequest.append(path);
        httpRequest.append(" ");
        httpRequest.append("HTTP/1.1");
        httpRequest.append("\r\n");
    }

    /**
     * 把传的headers map转为http请求头
     *
     * @param httpRequest 入参
     */
    private void getHttpHeaders(StringBuilder httpRequest) {
        getHeader(httpRequest, createHeaderMap());
    }

    /**
     * 处理post请求的请求体
     *
     * @param httpRequest
     * @param params
     */
    private void getHttpBody(StringBuilder httpRequest, String params) {
        if (mRequest.getMethod() == Method.POST && params != null && params.length() > 0) {
            httpRequest.append(params);
            httpRequest.append("\r\n");
        }
    }

    /**
     * 把传入的请求参数转为http请求体,包括请求path上的参数
     *
     * @return 所有的请求参数字符串
     */
    private String handleParams() {
        StringBuilder paramsBuilder = new StringBuilder();
        //获取请求path上的参数
        String query = mRequest.getUrl().getQuery();
        if (query != null) {
            paramsBuilder.append(query);
        }
        //获取额外的参数
        Map<String, String> params = mRequest.getParams();
        if (params != null) {
            paramsBuilder.append("&");
            for (Map.Entry<String, String> keyAndValue : params.entrySet()) {
                paramsBuilder.append("&");
                paramsBuilder.append(keyAndValue.getKey());
                paramsBuilder.append("=");
                paramsBuilder.append(keyAndValue.getValue());
            }
        }
        return paramsBuilder.toString();
    }

    /**
     * 添加默认的请求头,并把传入的请求头键值对返回
     *
     * @return 所有的请求头键值对
     */
    private Map<String, String> createHeaderMap() {
        //请求头
        Map<String, String> headerMap = mRequest.getHeaders();
        if (headerMap == null) {
            headerMap = new HashMap<>();
        }
        //主机
        headerMap.put("Host", mRequest.getHost());
        //代理 有些api没有这个请求头不让访问
        headerMap.put("User-Agent", "SocketRuntime/1.0");
        //Connection
//        headerMap.put("Connection", "keep-alive");
        //Accept-Encoding 压缩传输
//        headerMap.put("Accept-Encoding", "gzip, deflate");

        return headerMap;
    }

    /**
     * 把请求头键值对转为http请求头
     *
     * @param httpRequest 入参
     * @param headerMap   请求头键值对
     */
    private void getHeader(StringBuilder httpRequest, Map<String, String> headerMap) {
        if (headerMap == null) {
            return;
        }
        for (Map.Entry<String, String> keyAndValue : headerMap.entrySet()) {
            httpRequest.append(keyAndValue.getKey());
            httpRequest.append(":");
            httpRequest.append(keyAndValue.getValue());
            httpRequest.append("\r\n");
        }
        //空一行,代表请求头完结,之后的内容是请求体
        httpRequest.append("\r\n");
    }

}

4、实现效果

用gank.io提供的api测试一下:

/**
 * Description: 测试Socket实现的get请求
 * Author : pxq
 * Date : 19-12-28 下午5:59
 */
public class HttpTest {

    public static void main(String[] args) {
        HttpClient client = new HttpClient();

        String path = "http://gank.io/api/data/Android/10/1";
        final Request request = new Request(path);
        client.execute(request, new CallBack() {
            @Override
            public void onSuccess(Response response) {
                System.out.println(response.string());
            }

            @Override
            public void onError(Throwable throwable) {

            }
        });
    }
}

效果图:
在这里插入图片描述
这里打印了请求信息和返回的所有信息。还有响应也没处理,直接打印所有的返回了…

5、最后

虽然实现了,但是请求很慢(Socket连接都要好几秒)…不知道为什么,可能得好好了解http之后才懂吧。

发布了19 篇原创文章 · 获赞 25 · 访问量 5595

猜你喜欢

转载自blog.csdn.net/pxq10422/article/details/103758809