1. Overview
1.概述
Typically when managing the request and response cycle of the HTTP requests in our web applications, we’ll want a way to tap into this chain. Usually, this is to add some custom behaviour before we fulfill our requests or simply after our servlet code has completed.
通常在管理我们Web应用程序中HTTP请求的请求和响应周期时,我们会希望有一种方法来挖掘这个链。通常情况下。这就是要在我们满足请求之前添加一些自定义行为或简单地在我们的Servlet代码完成后。
OkHttp is an efficient HTTP & HTTP/2 client for Android and Java applications. In a previous tutorial, we looked at the basics of how to work with OkHttp.
OkHttp是一个适用于Android和Java应用程序的高效HTTP和HTTP/2客户端。在之前的教程中,我们研究了如何使用OkHttp的基础。
In this tutorial, we’ll learn all about how we can intercept our HTTP request and response objects.
在本教程中,我们将学习所有关于如何拦截我们的HTTP请求和响应对象的内容。
2. Interceptors
2.拦截器
As the name suggests, interceptors are pluggable Java components that we can use to intercept and process requests before they are sent to our application code.
顾名思义,拦截器是可插拔的Java组件,我们可以用它来拦截和处理请求,然后再把它们发送到我们的应用代码。
Likewise, they provide a powerful mechanism for us to process the server response before the container sends the response back to the client.
同样地,它们为我们提供了一个强大的机制,在容器将响应发回给客户端之前处理服务器响应。
This is particularly useful when we want to change something in an HTTP request, like adding a new control header, changing the body of our request, or simply producing logs to help us debug.
当我们想改变HTTP请求中的某些内容时,这一点特别有用,比如添加一个新的控制头,改变我们的请求主体,或者只是产生日志以帮助我们进行调试。
Another nice feature of using interceptors is that they let us encapsulate common functionality in one place. Let’s imagine we want to apply some logic globally to all our request and response objects, such as error handling.
使用拦截器的另一个好处是,它可以让我们把常用的功能封装在一个地方。假设我们想对所有的请求和响应对象应用一些全局逻辑,比如错误处理。
There are at least a couple of advantages of putting this kind of logic into an interceptor:
把这种逻辑放在拦截器中至少有几个好处。
- We only need to maintain this code in one place rather than all of our endpoints
- Every request made deals with the error in the same way
Finally, we can also monitor, rewrite, and retry calls from our interceptors as well.
最后,我们也可以对拦截器的调用进行监控、重写和重试。
3. Common Usage
3.常见用法
Some other common tasks when an inceptor might be an obvious choice include:
当拦截器可能是一个明显的选择时,其他一些常见的任务包括。
- Logging request parameters and other useful information
- Adding authentication and authorization headers to our requests
- Formatting our request and response bodies
- Compressing the response data sent to the client
- Altering our response headers by adding some cookies or extra header information
We’ll see a few examples of these in action in the proceeding sections.
在接下来的章节中,我们将看到一些实际的例子。
4. Dependencies
4.依赖性
Of course, we’ll need to add the standard okhttp dependency to our pom.xml:
当然,我们需要将标准的okhttp依赖性添加到我们的pom.xml。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
We’ll also need another dependency specifically for our tests. Let’s add the OkHttp mockwebserver artifact:
我们还需要另一个专门用于测试的依赖项。让我们添加OkHttp mockwebserver artifact。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.9.1</version>
<scope>test</scope>
</dependency>
Now that we have all the necessary dependencies configured, we can go ahead and write our first interceptor.
现在我们已经配置了所有必要的依赖关系,我们可以继续编写我们的第一个拦截器。
5. Defining a Simple Logging Interceptor
5.定义一个简单的日志拦截器
Let’s start by defining our own interceptor. To keep things really simple, our interceptor will log the request headers and the request URL:
让我们从定义我们自己的拦截器开始。为了让事情变得简单,我们的拦截器将记录请求头和请求的URL。
public class SimpleLoggingInterceptor implements Interceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLoggingInterceptor.class);
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
LOGGER.info("Intercepted headers: {} from URL: {}", request.headers(), request.url());
return chain.proceed(request);
}
}
As we can see, to create our interceptor, all we need to is inherit from the Interceptor interface, which has one mandatory method intercept(Chain chain). Then we can go ahead and override this method with our own implementation.
正如我们所看到的,为了创建我们的拦截器,我们只需要继承拦截器接口,它有一个强制性的方法拦截(Chain chain)。然后我们可以继续用我们自己的实现覆盖这个方法。
First, we get the incoming request by called chain.request() before printing out the headers and request URL.
首先,我们通过调用chain.request()获得传入的请求,然后打印出头信息和请求的URL。
It is important to note that a critical part of every interceptor’s implementation is the call to chain.proceed(request).
需要注意的是,每个拦截器实现的一个关键部分是对chain.proceed(request)的调用。
This simple-looking method is where we signal that we want to hit our application code, producing a response to satisfy the request.
这个看起来很简单的方法是我们发出信号的地方,我们想打我们的应用代码,产生一个响应来满足请求。
5.1. Plugging It Together
5.1.把它插在一起
To actually make use of this interceptor, all we need to do is call the addInterceptor method when we build our OkHttpClient instance, and it should just work:
为了实际利用这个拦截器,我们需要做的就是在构建我们的OkHttpClient实例时调用addInterceptor方法,它应该就能发挥作用。
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SimpleLoggingInterceptor())
.build();
We can continue to call the addInterceptor method for as many interceptors as we require. Just remember that they will be called in the order they added.
我们可以继续调用addInterceptor方法,需要多少个拦截器都可以。只要记住,它们将按照添加的顺序被调用。
5.2. Testing the Interceptor
5.2.测试拦截器
Now, we have defined our first interceptor; let’s go ahead and write our first integration test:
现在,我们已经定义了我们的第一个拦截器;让我们继续写我们的第一个集成测试。
@Rule
public MockWebServer server = new MockWebServer();
@Test
public void givenSimpleLogginInterceptor_whenRequestSent_thenHeadersLogged() throws IOException {
server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!"));
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SimpleLoggingInterceptor())
.build();
Request request = new Request.Builder()
.url(server.url("/greeting"))
.header("User-Agent", "A Baeldung Reader")
.build();
try (Response response = client.newCall(request).execute()) {
assertEquals("Response code should be: ", 200, response.code());
assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string());
}
}
First of all, we are using the OkHttp MockWebServer JUnit rule.
首先,我们正在使用OkHttp MockWebServer JUnit规则。
This is a lightweight, scriptable web server for testing HTTP clients that we’re going to use to test our interceptors. By using this rule, we’ll create a clean instance of the server for every integration test.
这是一个用于测试HTTP客户端的轻量级、可编写脚本的Web服务器,我们要用它来测试我们的拦截器。通过使用这个规则,我们将为每个集成测试创建一个干净的服务器实例。
With that in mind, let’s now walk through the key parts of our test:
考虑到这一点,现在让我们走过我们测试的关键部分。
- First of all, we set up a mock response that contains a simple message in the body
- Then, we build our OkHttpClient and configure our SimpleLoggingInterceptor
- Next, we set up the request we are going to send with one User-Agent header
- The last step is to send the request and verify the response code and body received is as expected
5.3. Running the Test
5.3.运行测试
Finally, when we run our test, we’ll see our HTTP User-Agent header logged:
最后,当我们运行我们的测试时,我们会看到我们的HTTP User-Agent头被记录。
16:07:02.644 [main] INFO c.b.o.i.SimpleLoggingInterceptor - Intercepted headers: User-Agent: A Baeldung Reader
from URL: http://localhost:54769/greeting
5.4. Using the Built-in HttpLoggingInterceptor
5.4.使用内置的HttpLoggingInterceptor
Although our logging interceptor demonstrates well how we can define an interceptor, it’s worth mentioning that OkHttp has a built-in logger that we can take advantage of.
尽管我们的日志拦截器很好地展示了我们如何定义一个拦截器,但值得一提的是,OkHttp有一个内置的日志器,我们可以利用它。
In order to use this logger, we need an extra Maven dependency:
为了使用这个记录器,我们需要一个额外的Maven依赖项。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>4.9.1</version>
</dependency>
Then we can go ahead and instantiate our logger and define the logging level we are interested in:
然后我们就可以去实例化我们的日志器,并定义我们感兴趣的日志级别。
HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
logger.setLevel(HttpLoggingInterceptor.Level.HEADERS);
In this example, we are only interested in seeing the headers.
在这个例子中,我们只对看到标题感兴趣。
6. Adding a Custom Response Header
6.添加自定义响应头
Now that we understand the basics behind creating interceptors. Let’s now take a look at another typical use case where we modify one of the HTTP response headers.
现在我们已经了解了创建拦截器背后的基础知识。现在让我们来看看另一个典型的用例,在这个用例中,我们要修改HTTP响应头中的一个。
This can be useful if we want to add our own proprietary application HTTP header or rewrite one of the headers coming back from our server:
如果我们想添加我们自己专有的应用程序HTTP头或重写从我们的服务器回来的一个头,这可能是有用的。
public class CacheControlResponeInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
return response.newBuilder()
.header("Cache-Control", "no-store")
.build();
}
}
As before, we call the chain.proceed method but this time without using the request object beforehand. When the response comes back, we use it to create a new response and set the Cache-Control header to no-store.
和以前一样,我们调用chain.proceed方法,但这次没有事先使用请求对象。当响应回来时,我们使用它来创建一个新的响应,并将Cache-Control头设为no-store。
In reality, it’s unlikely we’ll want to tell the browser to pull from the server each time, but we can use this approach to set any header in our response.
在现实中,我们不太可能每次都告诉浏览器从服务器中提取,但我们可以用这种方法来设置我们响应中的任何头。
7. Error Handling Using Interceptors
7.使用拦截器处理错误
As mentioned previously, we can also use interceptors to encapsulate some logic that we want to apply globally to all our request and response objects, such as error handling.
如前所述,我们也可以使用拦截器来封装一些我们想全局性地应用于所有请求和响应对象的逻辑,比如错误处理。
Let’s imagine we want to return a lightweight JSON response with the status and message when the response is not an HTTP 200 response.
让我们设想一下,当响应不是HTTP 200响应时,我们想返回一个带有状态和消息的轻量级JSON响应。
With that in mind, we’ll start by defining a simple bean for holding the error message and status code:
考虑到这一点,我们将首先定义一个简单的bean来保存错误信息和状态代码。
public class ErrorMessage {
private final int status;
private final String detail;
public ErrorMessage(int status, String detail) {
this.status = status;
this.detail = detail;
}
// Getters and setters
}
Next, we’ll create our interceptor:
接下来,我们将创建我们的拦截器。
public class ErrorResponseInterceptor implements Interceptor {
public static final MediaType APPLICATION_JSON = MediaType.get("application/json; charset=utf-8");
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (!response.isSuccessful()) {
Gson gson = new Gson();
String body = gson.toJson(
new ErrorMessage(response.code(), "The response from the server was not OK"));
ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON);
ResponseBody originalBody = response.body();
if (originalBody != null) {
originalBody.close();
}
return response.newBuilder().body(responseBody).build();
}
return response;
}
}
Quite simply, our interceptor checks to see if the response was successful and if it wasn’t, creates a JSON response containing the response code and a simple message. Note, in this case, we must remember to close the original response’s body, to release any resources associated with it.
很简单,我们的拦截器检查响应是否成功,如果不成功,就创建一个包含响应代码和简单消息的JSON响应。注意,在这种情况下,我们必须记得关闭原始响应的主体,以释放与之相关的任何资源。
{
"status": 500,
"detail": "The response from the server was not OK"
}
8. Network Interceptors
8.网络拦截器
So far, the interceptors we have covered are what OkHttp refers to as Application Interceptors. However, OkHttp also has support for another type of interceptor called Network Interceptors.
到目前为止,我们所涉及的拦截器是OkHttp所说的应用拦截器。然而,OkHttp也支持另一种类型的拦截器,称为网络拦截器。
We can define our network interceptors in exactly the same way as explained previously. However, we’ll need to call the addNetworkInterceptor method when we create our HTTP client instance:
我们可以用与前面解释的完全相同的方式来定义我们的网络拦截器。但是,我们需要在创建HTTP客户端实例时调用addNetworkInterceptor方法。
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new SimpleLoggingInterceptor())
.build();
Some of the important differences between application and network inceptors include:
应用和网络拦截器之间的一些重要区别包括。
- Application interceptors are always invoked once, even if the HTTP response is served from the cache
- A network interceptor hooks into the network level and is an ideal place to put retry logic
- Likewise, we should consider using a network interceptor when our logic doesn’t rely on the actual content of the response
- Using a network interceptor gives us access to the connection that carries the request, including information like the IP address and TLS configuration that was used to connect to the webserver.
- Application interceptors don’t need to worry about intermediate responses like redirects and retries
- On the contrary, a network interceptor might be invoked more than once if we have a redirection in place
As we can see, both options have their own merits. So it really depends on our own particular use case for which one we’ll choose.
正如我们所看到的,两种选择都有各自的优点。因此,这真的取决于我们自己的特定用例,我们将选择哪一种。
However, more often than not, application interceptors will do the job just fine.
然而,更多时候,应用拦截器可以很好地完成工作。
9. Conclusion
9.结语
In this article, we’ve learned all about how to create interceptors using OkHttp. First, we began by explaining what an interceptor is and how we can define a simple logging interceptor to inspect our HTTP request headers.
在这篇文章中,我们已经了解了如何使用OkHttp创建拦截器的全部内容。首先,我们开始解释了什么是拦截器,以及我们如何定义一个简单的日志拦截器来检查我们的HTTP请求头。
Then we saw how to set a header and also a different body into our response objects. Finally, we took a quick look at some of the differences between application and network interceptors.
然后我们看到如何在我们的响应对象中设置一个头和不同的主体。最后,我们快速看了一下应用和网络拦截器之间的一些区别。
As always, the full source code of the article is available over on GitHub.
一如既往,该文章的完整源代码可在GitHub上获得。