美文网首页
第一章 基础(1)

第一章 基础(1)

作者: WillReading | 来源:发表于2019-10-06 21:49 被阅读0次

    1.1 执行请求

    HttpClient最基本的功能是执行HTTP方法。一个HTTP方法的执行涉及到一个或多个HTTP请求和响应的交换,这通常是在HttpClient内部处理的。用户需要提供一个请求对象,HttpClient负责传输这个请求到目标服务器并返回相对应的响应的对象,如果执行不成功,则抛出异常。

    HttpClient API的入口就是HttpClient接口。

    以下是最简单的形式的执行请求的例子:

        public void chapter1_1() throws IOException {
    
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet("http://www.baidu.com");
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                // handle response
            } finally {
                response.close();
            }
    
        }
    

    1.1.1 HTTP请求

    所有的HTTP请求都有一个请求头,其中包含方法名请求URIHTTP协议版本号

    HttpClient支持所有HTTP/1.1规格中定义的HTTP方法,包括:GET,HEAD, POST, PUT, DELETE, TRACEOPTIONS。每种方法都有与之对应的类:HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTraceHttpOptions

    请求URI是请求资源的统一资源描述符(Uniform Resource Identifier)。HTTP请求URI包含协议(protocol schema)、主机名(host name)、可选的端口(optional port)、资源路径(resource path)、可选的查询(optional query)和可选的分块(optional fragment)。

    HttpGet httpGet = new HttpGet(
                    "http://www.google.com/search?h1=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
    

    HttpClient 提供 URIBuilder工具类来简化创建和修改请求URI:

          URI uri = new URIBuilder()
                    .setScheme("http")
                    .setHost("www.google.com")
                    .setPath("/search")
                    .setParameter("h1", "en")
                    .setParameter("q", "httpclient")
                    .setParameter("btnG", "Google+Search")
                    .setParameter("aq", "f")
                    .setParameter("oq", "")
                    .build();
            HttpGet httpGet1 = new HttpGet(uri);
            System.out.println(httpGet1.getURI());
    

    1.1.2 HTTP响应

    HTTP响应是服务器接收和处理完请求报文后所返回的报文。报文的第一行由协议版本、状态码和及其描述组成。

        public void chapter1_1_2() throws Exception {
            HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
    
            System.out.println(response.getProtocolVersion());
            System.out.println(response.getStatusLine().getStatusCode());
            System.out.println(response.getStatusLine().getReasonPhrase());
            System.out.println(response.getStatusLine().toString());
        }
    

    输出>

    HTTP/1.1
    200
    OK
    HTTP/1.1 200 OK
    

    1.1.3 报文头部

    HTTP报文包含一些用于描述报文的头部信息,如内容长度、内容类型等等 。HttpClient提供方法去获取、添加、移除和列举这些头部信息。

        public void chapter1_1_3() {
            HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
            response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
            response.addHeader("Set-Cookie", "c2=b; path=\"/\"; domain=\"localhost\"");
    
            Header h1 = response.getFirstHeader("Set-Cookie");
            System.out.println(h1);
    
            Header h2 = response.getLastHeader("Set-Cookie");
            System.out.println(h2);
    
            Header[] hs = response.getHeaders("Set-Cookie");
            System.out.println(hs.length);
        }
    

    输出>

    Set-Cookie: c1=a; path=/; domain=localhost
    Set-Cookie: c2=b; path="/"; domain="localhost"
    2
    

    获取所有给定类型头部信息最有效方式是使用HeaderIterator接口。

        public void chapter1_1_3_2() {
            HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
            response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
            response.addHeader("Set-Cookie", "c2=b; path=\"/\"; domain=\"localhost\"");
    
            BasicHeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Set-Cookie"));
            while (it.hasNext()) {
                HeaderElement elem = it.nextElement();
                System.out.println(elem.getName() + " = " + elem.getValue());
                NameValuePair[] params = elem.getParameters();
                for (NameValuePair param : params) {
                    System.out.println(" " + param);
                }
            }
        }
    

    输出>

    c1 = a
     path=/
     domain=localhost
    c2 = b
     path=/
     domain=localhost
    

    1.1.4 HTTP实体

    HTTP报文可以携带与请求或响应相关联的内容实体。因为实体是可选的,并不是所有的请求和响应中都包含实体。使用实体的请求被称为包含实体的请求(entity enclosing request)。HTTP规格中定义了2种包含实体的请求的方法:POSTPUT
    响应通常会包含一个内容实体。但也是例外,如HEAD方法的响应和 204 No Content, 304 Not Modified, 205 Reset Content 响应。

    HttpClient区分三种实体类型,取决于它们内容的来源:

    • streamed: 内容是从流(stream)中获得,或联机生成的。这里包含从HTTP响应中的实体。流式实体(streamed entities)通常是不能重复的。
    • self-contained: 内容是在内存里。自包含实体(self-contained entities)通常是可重复的。这种类型的实体最多用于包含实体的请求(entity enclosing request)
    • wrapping: 内容从另一实体获得。

    当从HTTP响应中获取数据流时,这些区分对于连接管理来说是重要的。对于应用创建的请求实体,且仅使用HttpClient来发送,streamed 还是 self-contained 的区别就不重要了。这种情况下,建议把不可重复的实体归为streamed类型,可重复的为self-contained类型。

    1.1.4.1 可重复实体

    可重复实体是指它的内容能够被重复读取。只有自包含实体(self-contained entities)才是可重复的(如ByteArrayEntityStringEntity)。

    1.1.4.2 使用HTTP实体

    因为实体可表示二进制和字符内容,所以它是支持字符编码的。
    实体被创建的时机有 a) 执行包含内容的请求; b) 请求成功后,响应体使用实体将结果返回。

    为了从输入报文的实体中读取内容,我们可以通过HttpEntity#getContent()方法获取输入流java.io.InputStream, 或者我们可以通过HttpEntity#writeTo(OutpusStream)方法将其写到另一个给定的输出流中。

    当从响应报文中接收到实体后,HttpEntity#getContentTypeHttpEntity#getContentLength方法可以用来读取通用的元数据,如Content-TypeContent-Length头部信息(如果有的话)。Content-Type头部对于文本类型的多媒体类型(如text/plaintext/html)来说可能包含字符编码的信息,HttpEntity#getContentEncoding()方法可能用来读取该信息。如果头部不可用的话,HttpEntity#getContentLength返回-1,HttpEntity#getContentType返回NULL。如果Content-Type可用的话,Header对象将会被返回。

    当给输出报文创建实体,元数据必须使用实体的创建者方法来创建:

        public void chapter1_1_4() throws IOException {
            StringEntity myEntity = new StringEntity("important message",
                    ContentType.create("text/plain", "UTF-8"));
    
            System.out.println(myEntity.getContentType());
            System.out.println(myEntity.getContentLength());
            System.out.println(EntityUtils.toString(myEntity));
            System.out.println(EntityUtils.toByteArray(myEntity).length);
        }
    

    输出>

    Content-Type: text/plain; charset=UTF-8
    17
    important message
    17
    

    1.1.5 确保释放底层资源

    为了确保正确地的释放系统资源,我们必须关闭实体关联的内容流(stream)或者响应(response)本身。

        public void chapter1_1_5() throws Exception {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet("http://www.baidu.com");
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                HttpEntity entity = response.getEntity();
                if (Objects.nonNull(entity)) {
                    InputStream inputStream = entity.getContent();
                    try {
                        // do something with inputStream
                    } finally {
                        inputStream.close();
                    }
                }
            } finally {
                response.close();
            }
        }
    

    关闭内容流和关闭响应的区别在于,前者通过消费实体内容来试图保持底层连接,后者会立即关闭并且丢弃该连接。

    请注意HttpEntity#writeTo(OUtputStream)方法也需要确保正确释放系统资源。如果这个方法通过调用HttpEntity#getContent方法来获取的java.io.InputStream实例,这也需要在一个finally子句中将其关闭。

    我们也可以使用EntityUtils#consume(HttpEntity)方法来确认实体内容被完全消费并且底层流被关闭。

    有一种情况,如果仅需要读取响应中的一部分内容,并且报文剩余内容的性能代价和保持连接的代价太高的话,我们可以通过关闭响应来结束内容流。

        public void chapter1_1_5_1() throws Exception {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet("http://www.baidu.com");
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                HttpEntity entity = response.getEntity();
                if (Objects.nonNull(entity)) {
                    InputStream inputStream = entity.getContent();
                    int byteOne = inputStream.read();
                    int byteTwo = inputStream.read();
                    // Do not need the rest
                }
            } finally {
                response.close();
            }
        }
    

    连接将不会被重用,而且被该连接持有的所有资源将会被正确地释放。

    1.1.6 消费实体内容

    消费一个实体的内容推荐的方法是使用HttpEntity#getContent()HttpEntity#writeTo(OutpusStream)方法。HttpClient也包含EntityUtils类,其他包含一些静态方法可以理容易地读取实体内容或信息。这样我们就可以使用这个类的方法来读取整个字符或字节数据内容,而不是直接操作java.io.InputStream。然而,EntityUtils的使用是非常不推荐的,除非响应实体是来源于一个受信的HTTP服务器并且内容的长度是有限的。

        public void chapter1_1_6() throws Exception {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet("http://www.baidu.com");
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                HttpEntity entity = response.getEntity();
                if (Objects.nonNull(entity)) {
                    long len = entity.getContentLength();
                    if (len != -1 && len < 2048) {
                        System.out.println(EntityUtils.toString(entity));
                    } else {
                        // Stream content out
                        // content length is too large
                    }
                }
            } finally {
                response.close();
            }
        }
    

    在某些情况下,我们需要重复读取实体内容。此时,实体内容必须用某种方式来缓冲,或者在内容或者在磁盘中。完成缓存的最简单方式就是把原始的实体用BufferedHttpEntity类来包装。这能够使原来的实体被读进内存的缓存区中。

    CloseableHttpResponse response = <...>
    HttpEntity entity = response.getEntity();
    if (entity != null) {
      entity = new BufferedHttpEntity(entity);
    }
    

    1.1.7 生产实体内容

    HttpClient提供一些类,这些类用来高效地将实体内容通过HTTP连接输出到流。这些类的实例能够与包含实体的请求关联, 如POSTPUT。HttpClient提供一些类来作为最常见的数据的容器,如字符串、字节数组、输入流和文件:StringEntityByteArrayEntityInputStreamEntityFileEntity

        public void chapter1_1_7() throws Exception {
            File file = new File("somefile.txt");
            FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));
    
            HttpPost httpPost = new HttpPost("http://localhost/action.do");
            httpPost.setEntity(entity);
        }
    

    请注意InputStreamEntity不是可重复的,因为它只能从底层数据流中读取一次。通常推荐去实现一个自定义的HttpEntity,使其成为self-contained类型的,而不是去使用InputStreamEntityFileEntity就是一个很好的例子。

    1.1.7.1 HTML表单

    很多应用需要去模拟提交HTML表单的过程,例如,为了登陆或提交输入数据。HttpClient提供实体类UrlEncodedFormEntity来帮助这个提交过程。

        public void chapter1_1_7_1() throws Exception {
            List<NameValuePair> formParams = new ArrayList<>();
            formParams.add(new BasicNameValuePair("param1", "value1"));
            formParams.add(new BasicNameValuePair("param2", "value2"));
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
            
            HttpPost httpPost = new HttpPost("http://localhost/handle.do");
            httpPost.setEntity(entity);
        }
    

    UrlEncodedFormEntity会使用URL编码来对参数进行编码,并且产生如下内容:

    param1=value1&param2=value2
    

    1.1.7.2 内容分块(Content chunking)

    通常推荐让HttpClient基于传输的HTTP报文的属性去选择使用最合适的传输编码。然而,通过设置HttpEntity#setChunked()true来通知HttpClient使用chunk编码是可能的。当然,这仅仅只是一个提示而已。如果使用不支持chunk编码的HTTP协议,如HTTP/1.0,该值将会被忽略。

    1.1.8 响应处理器(Response handlers)

    最简单并且最方便的方式去处理响应是使用ResponseHandler接口,该接口包含handleResponse(HttpResponse response)方法。该方法完全地把用户从连接管理中解放出来。当使用ResponseHandler,HttpClient会自动地的确保连接会被释放回给连接管理器,不管执行请求是否成功或异常。

        public void chapter1_1_8() throws Exception {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpGet httpGet = new HttpGet("http://www.baidu.com");
    
            ResponseHandler<MyJsonObject> rh = response -> {
                StatusLine statusLine = response.getStatusLine();
                HttpEntity entity = response.getEntity();
                if (statusLine.getStatusCode() >= 300) {
                    throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
                }
                if (Objects.isNull(entity)) {
                    throw new ClientProtocolException("Response contains no content");
                }
    
                Gson gson = new GsonBuilder().create();
                ContentType contentType = ContentType.getOrDefault(entity);
                Charset charset = contentType.getCharset();
                Reader reader = new InputStreamReader(entity.getContent(), charset);
                return gson.fromJson(reader, MyJsonObject.class);
    
            };
            
            MyJsonObject myJsonObject = httpClient.execute(httpGet, rh);
    
        }
        
        static class MyJsonObject {
            String name;
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
        }
    

    相关文章

      网友评论

          本文标题:第一章 基础(1)

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