ZetCode

Jetty 的 HTTPClient

最后修改于 2024 年 1 月 27 日

Jetty 的 HTTP client 是一个用于执行 HTTP 和 HTTPS 请求的模块。它可以用于创建异步和同步请求。用于执行 HTTP 请求的 Java 类名为 HttpClient

HttpClient 本质上是异步的。发送请求的代码在继续之前不会等待响应到达。

HTTP GET 方法

HTTP GET 方法是用于从服务器检索信息的请求。HttpClient 类有一个同名的 GET 方法来执行此类请求。GET 方法是同步发送请求的——它会阻塞处理直到请求完成。

package com.zetcode;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;

public class HttpClientGetMethod {

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

        ContentResponse res = client.GET("http://www.something.com");
        
        System.out.println(res.getContentAsString());
        
        client.stop();
    }
}

我们的示例检索指定主页的内容。

HttpClient client = new HttpClient();
client.start();

我们创建一个 HttpClient 实例。start 方法启动其生命周期。

ContentResponse res = client.GET("http://www.something.com");

我们从指定网站检索一个 HTML 页面。数据以 ContentResponse 的形式发送。默认情况下,内容长度限制为 2 MB。

System.out.println(res.getContentAsString());

从响应中,我们使用 getContentAsString 方法获取内容。

client.stop();

stop 方法结束 HTTP client 的生命周期。

$ javac -d bin -cp :../lib/jetty-all-9.2.2.jar com/zetcode/HttpClientGetMethod.java 
$ java -cp :bin:../lib/jetty-all-9.2.2.jar com.zetcode.HttpClientGetMethod 
2014-09-02 23:06:24.279:INFO::main: Logging initialized @132ms
<html><head><title>Something.</title></head>
<body>Something.</body>
</html>

我们编译并运行示例。我们使用一个聚合的 jetty-all JAR 文件,可以从 Maven 仓库下载。

HEAD 请求

HEAD 方法与 GET 方法相同,只是服务器在响应中不返回消息体。如果我们想自定义请求,可以使用 HttpClientnewRequest 方法。

package com.zetcode;

import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;

public class HttpClientHead {

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

        Response response = client.newRequest("www.google.com", 80)
                .scheme("http")
                .agent("Jetty HTTP client")
                .version(HttpVersion.HTTP_1_1)
                .method(HttpMethod.HEAD)
                .timeout(5, TimeUnit.SECONDS)
                .send();

        System.out.println(response.getStatus());

        client.stop();       
    }
}

该示例向指定网站发送一个 HEAD 请求。我们从响应中获取状态码。

Response response = client.newRequest("www.google.com", 80)
        .scheme("http")
        .agent("Jetty HTTP client")
        .version(HttpVersion.HTTP_1_1)
        .method(HttpMethod.HEAD)
        .timeout(5, TimeUnit.SECONDS)
        .send();

请求是通过在请求对象上链式调用方法来定制的。例如,agent 方法指定发起请求的客户端软件。method 指定要执行的 HTTP 方法。通过将 HttpMethod.HEAD 作为参数传递,我们创建了一个 HTTP HEAD 请求。

由于 HEAD 请求只返回头部而不返回内容,我们使用 Response 类型。如果我们对内容感兴趣,假设该方法返回了内容,我们则使用 ContentResponse 类型。

$ javac -d bin -cp ../lib/jetty-all-9.2.2.jar com/zetcode/HttpClientHead.java 
$ java -cp :bin:../lib/jetty-all-9.2.2.jar com.zetcode.HttpClientHead 
2014-09-03 14:18:38.403:INFO::main: Logging initialized @138ms
200

该示例返回 HTTP 200 状态码,这是一个成功的响应的标准代码。

异步请求

异步请求是 것입니다请求-响应对话结束的非阻塞请求。在 Jetty 中,异步请求是使用监听器实现的。监听器在请求和响应处理的各个阶段被调用。例如,当对话结束时(无论成功与否),都会调用 CompleteListener

package com.zetcode;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;

public class HttpClientAsync {

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

        client.newRequest("http://www.something.com")
                .send(new Response.CompleteListener()
                {
                    @Override
                    public void onComplete(Result result)
                    {
                        System.out.println(result.getResponse().getStatus());
                        System.out.println("Request completed");
                    }
                });
        
        //client.stop();       
    }
}

一个异步 GET 请求被发送到一个指定的网站。

client.newRequest("http://www.something.com")
        .send(new Response.CompleteListener()
        {

通过将监听器传递给 send 方法来创建异步请求。

@Override
public void onComplete(Result result)
{
    System.out.println(result.getResponse().getStatus());
    System.out.println("Request completed");
}

onComplete 方法是一个回调方法,在请求-响应对话结束后被调用。

//client.stop();       

在非阻塞处理中,调用 stop 可能会导致异常。该方法必须在 client 线程之外调用。应用程序必须通过 Ctrl+C 组合键或显式杀死进程来结束。下一个示例提供了一个更干净的解决方案。

CountDownLatch

CountDownLatch 是一个同步辅助工具,它允许一个线程等待直到另一个线程中正在执行的一组操作完成。

package com.zetcode;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;

public class HttpClientLatch {

    public static void main(String[] args) throws Exception {

        HttpClient client = new HttpClient();
        client.start();

        final AtomicReference<Response> responseRef = new AtomicReference<>();
        final CountDownLatch latch = new CountDownLatch(1);

        client.newRequest("http://www.something.com")
                .send(new Response.CompleteListener() {

                    @Override
                    public void onComplete(Result result) {
                        if (result.isSucceeded()) {
                            responseRef.set(result.getResponse());
                            latch.countDown();
                        }
                    }
                });

        boolean val = latch.await(10, TimeUnit.SECONDS);
        
        if (!val) {
            System.out.println("Time has elapsed.");
            System.exit(1);
        }
        
        Response response = responseRef.get();
        System.out.println(response.getStatus());
        
        client.stop();
    }
}

该示例将一个异步 GET 请求发送到一个指定的网站。借助 CountDownLatch 类,我们等待响应到达并停止 client。我们不必杀死应用程序。

final AtomicReference<Response> responseRef = new AtomicReference<>();

当我们只需要在多个线程之间共享一个不可变对象以确保其正确性时,可以使用 AtomicReference。我们的代码共享一个对响应对象的引用。

final CountDownLatch latch = new CountDownLatch(1);

CountDownLatch 接受一个数字作为参数。它表示在线程可以通过 await 方法之前,countDown 必须被调用多少次。

responseRef.set(result.getResponse());

响应被设置为原子引用。

latch.countDown();

countDown 方法递减 latch 的计数。如果新计数为零,则所有等待的线程都将被重新启用以进行线程调度。

boolean val = latch.await(10, TimeUnit.SECONDS);

await 方法使当前线程等待,直到 latch 倒计时到零,除非线程被中断,或者指定的等待时间已过。如果计数达到零,该方法返回 true。如果指定的等待时间已过,则返回 false。

if (!val) {
    System.out.println("Time has elapsed.");
    System.exit(1);
}

如果时间已过,我们会向控制台打印一条消息并退出应用程序。

Response response = responseRef.get();
System.out.println(response.getStatus());

如果一切顺利,我们获取响应对象并将状态码打印到控制台。

POST 请求

HTTP POST 请求用于将数据发送到服务器。POST 请求是使用 HttpClientFORM 方法创建的。

package com.zetcode;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Fields.Field;

class MyHandler extends AbstractHandler {

    @Override
    public void handle(String target, Request baseRequest,
            HttpServletRequest request,
            HttpServletResponse response)
            throws IOException, ServletException {

        response.setContentType("text/plain;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        baseRequest.setHandled(true);

        PrintWriter out = response.getWriter();
        
        for (Enumeration<String> e = baseRequest.getParameterNames(); 
                e.hasMoreElements();) {
            String name = e.nextElement();
            out.format("%s: %s%n", name, baseRequest.getParameter(name));
        }
    }
}

public class HttpClientSendForm {

    private Server server;
    private HttpClient client;

    private void startServer() throws Exception {

        server = new Server(8080);
        server.setHandler(new MyHandler());
        server.start();
    }

    private void startClient() throws Exception {

        client = new HttpClient();
        client.start();

        Field name = new Field("Name", "Robert");
        Field age = new Field("Age", "32");
        Fields fields = new Fields();
        fields.put(name);
        fields.put(age);

        ContentResponse res = client.FORM("https://:8080", fields);
        System.out.println(res.getContentAsString());
    }

    private void stopClientServer() throws Exception {
        client.stop();
        server.stop();
    }

    public static void main(String[] args) throws Exception {

        HttpClientSendForm smp = new HttpClientSendForm();
        smp.startServer();
        smp.startClient();
        smp.stopClientServer();
    }
}

该示例创建一个本地服务器,并向其发送表单数据。

PrintWriter out = response.getWriter();

for (Enumeration<String> e = baseRequest.getParameterNames(); 
        e.hasMoreElements();) {
    String name = e.nextElement();
    out.format("%s: %s%n", name, baseRequest.getParameter(name));
}

在服务器的 handler 中,我们使用 getParameterNames 方法获取请求参数。我们遍历它们并将它们打印到输出写入器。

Field name = new Field("Name", "Robert");
Field age = new Field("Age", "32");
Fields fields = new Fields();
fields.put(name);
fields.put(age);

创建请求参数名及其值;将它们放入字段中。

ContentResponse res = client.FORM("https://:8080", fields);

FORM 方法将字段发送到本地服务器。

System.out.println(res.getContentAsString());

ContentResponse 中,我们使用 getContentAsString 方法获取内容。

在本章的 Jetty 教程中,我们介绍了 Jetty 的 HttpClient

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。到目前为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有Java教程