Penapis dan Pemintas Jersey

1. Pengenalan

Dalam artikel ini, kami akan menerangkan bagaimana penapis dan pemintas berfungsi dalam kerangka Jersey, serta perbezaan utama antara ini.

Kami akan menggunakan Jersey 2 di sini, dan kami akan menguji aplikasi kami menggunakan pelayan Tomcat 9.

2. Persediaan Aplikasi

Mari buat sumber pertama di pelayan kami:

@Path("/greetings") public class Greetings { @GET public String getHelloGreeting() { return "hello"; } }

Juga, mari buat konfigurasi pelayan yang sesuai untuk aplikasi kami:

@ApplicationPath("/*") public class ServerConfig extends ResourceConfig { public ServerConfig() { packages("com.baeldung.jersey.server"); } }

Sekiranya anda ingin menggali lebih mendalam tentang cara membuat API dengan Jersey, anda boleh membaca artikel ini.

Anda juga boleh melihat artikel yang berfokus pada pelanggan kami dan belajar bagaimana membuat klien Java dengan Jersey.

3. Penapis

Sekarang, mari mulakan dengan penapis.

Sederhananya, penapis membolehkan kita mengubah sifat permintaan dan respons - contohnya, tajuk HTTP. Penapis boleh digunakan di pelayan dan pelanggan.

Perlu diingat bahawa penapis selalu dijalankan, tidak kira sama ada sumber itu dijumpai atau tidak.

3.1. Melaksanakan Penapis Pelayan Permintaan

Mari mulakan dengan penapis di sisi pelayan dan buat penapis permintaan.

Kami akan melakukannya dengan melaksanakan antara muka ContainerRequestFilter dan mendaftarkannya sebagai Penyedia di pelayan kami:

@Provider public class RestrictedOperationsRequestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext ctx) throws IOException { if (ctx.getLanguage() != null && "EN".equals(ctx.getLanguage() .getLanguage())) { ctx.abortWith(Response.status(Response.Status.FORBIDDEN) .entity("Cannot access") .build()); } } }

Penapis sederhana ini hanya menolak permintaan dengan bahasa "EN" dalam permintaan dengan memanggil kaedah abortWith () .

Seperti yang ditunjukkan oleh contoh, kita harus menerapkan hanya satu metode yang menerima konteks permintaan, yang dapat kita ubah mengikut keperluan kita.

Ingatlah bahawa penapis ini dijalankan setelah sumber dipadankan.

Sekiranya kami ingin melaksanakan penapis sebelum pemadanan sumber, kami dapat menggunakan penapis pra-pencocokan dengan memberi penjelasan pada penapis kami dengan anotasi @PreMatching :

@Provider @PreMatching public class PrematchingRequestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext ctx) throws IOException { if (ctx.getMethod().equals("DELETE")) { LOG.info("\"Deleting request"); } } }

Sekiranya kami cuba mengakses sumber kami sekarang, kami dapat memeriksa bahawa penapis pra-padanan kami dijalankan terlebih dahulu:

2018-02-25 16:07:27,800 [http-nio-8080-exec-3] INFO c.b.j.s.f.PrematchingRequestFilter - prematching filter 2018-02-25 16:07:27,816 [http-nio-8080-exec-3] INFO c.b.j.s.f.RestrictedOperationsRequestFilter - Restricted operations filter

3.2. Melaksanakan Penapis Pelayan Respons

Kami sekarang akan menerapkan penapis tindak balas di sisi pelayan yang hanya akan menambahkan tajuk baru untuk respons.

Untuk melakukan itu, penapis kami harus melaksanakan antara muka ContainerResponseFilter dan melaksanakan satu-satunya kaedahnya:

@Provider public class ResponseServerFilter implements ContainerResponseFilter { @Override public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { responseContext.getHeaders().add("X-Test", "Filter test"); } }

Perhatikan bahawa parameter ContainerRequestContext hanya digunakan sebagai baca sahaja - kerana kami sudah memproses respons.

2.3. Melaksanakan Penapis Pelanggan

Kami akan bekerja sekarang dengan penapis di pihak pelanggan. Penapis ini berfungsi dengan cara yang sama seperti penapis pelayan, dan antara muka yang harus kita laksanakan sangat serupa dengan yang ada di sisi pelayan.

Mari kita melihatnya beraksi dengan penapis yang menambahkan harta pada permintaan:

@Provider public class RequestClientFilter implements ClientRequestFilter { @Override public void filter(ClientRequestContext requestContext) throws IOException { requestContext.setProperty("test", "test client request filter"); } }

Mari juga buat pelanggan Jersey untuk menguji penapis ini:

public class JerseyClient { private static String URI_GREETINGS = "//localhost:8080/jersey/greetings"; public static String getHelloGreeting() { return createClient().target(URI_GREETINGS) .request() .get(String.class); } private static Client createClient() { ClientConfig config = new ClientConfig(); config.register(RequestClientFilter.class); return ClientBuilder.newClient(config); } }

Perhatikan bahawa kita harus menambahkan saringan ke konfigurasi klien untuk mendaftarkannya.

Akhirnya, kami juga akan membuat penapis untuk tindak balas pelanggan.

Ini berfungsi dengan cara yang sangat serupa dengan yang ada di pelayan, tetapi melaksanakan antara muka ClientResponseFilter :

@Provider public class ResponseClientFilter implements ClientResponseFilter { @Override public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { responseContext.getHeaders() .add("X-Test-Client", "Test response client filter"); } }

Sekali lagi, ClientRequestContext adalah untuk tujuan baca sahaja.

4. Pemintas

Interceptors lebih berkaitan dengan marshalling dan unmarshalling dari badan mesej HTTP yang terkandung dalam permintaan dan respons. Mereka boleh digunakan baik di pelayan dan di sisi pelanggan.

Perlu diingat bahawa ia dilaksanakan selepas penapis dan hanya jika isi mesej ada.

Terdapat dua jenis pemintas: ReaderInterceptor dan WriterInterceptor , dan ia sama untuk kedua-dua pelayan dan pelanggan.

Seterusnya, kami akan membuat sumber lain di pelayan kami - yang diakses melalui POST dan menerima parameter dalam badan, jadi pemintas akan dilaksanakan ketika mengaksesnya:

@POST @Path("/custom") public Response getCustomGreeting(String name) { return Response.status(Status.OK.getStatusCode()) .build(); }

Kami juga akan menambahkan kaedah baru untuk pelanggan Jersey kami - untuk menguji sumber baru ini:

public static Response getCustomGreeting() { return createClient().target(URI_GREETINGS + "/custom") .request() .post(Entity.text("custom")); }

4.1. Melaksanakan ReaderInterceptor

Pemintas pembaca membolehkan kami memanipulasi aliran masuk, jadi kami dapat menggunakannya untuk mengubah permintaan di sisi pelayan atau respons di pihak klien.

Mari buat pencegat di bahagian pelayan untuk menulis mesej tersuai di badan permintaan yang dipintas:

@Provider public class RequestServerReaderInterceptor implements ReaderInterceptor { @Override public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException { InputStream is = context.getInputStream(); String body = new BufferedReader(new InputStreamReader(is)).lines() .collect(Collectors.joining("\n")); context.setInputStream(new ByteArrayInputStream( (body + " message added in server reader interceptor").getBytes())); return context.proceed(); } }

Perhatikan bahawa kita perlu memanggil meneruskan () kaedah untuk memanggil pemintas seterusnya dalam rantaian . Setelah semua pemintas dijalankan, pembaca badan mesej yang sesuai akan dipanggil.

3.2. Implementing a WriterInterceptor

Writer interceptors work in a very similar way to reader interceptors, but they manipulate the outbound streams – so that we can use them with the request in the client side or with the response in the server side.

Let's create a writer interceptor to add a message to the request, on the client side:

@Provider public class RequestClientWriterInterceptor implements WriterInterceptor { @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { context.getOutputStream() .write(("Message added in the writer interceptor in the client side").getBytes()); context.proceed(); } }

Again, we have to call the method proceed() to call the next interceptor.

When all the interceptors are executed, the appropriate message body writer will be called.

Don't forget that you have to register this interceptor in the client configuration, as we did before with the client filter:

private static Client createClient() { ClientConfig config = new ClientConfig(); config.register(RequestClientFilter.class); config.register(RequestWriterInterceptor.class); return ClientBuilder.newClient(config); }

5. Execution Order

Let's summarize all that we've seen so far in a diagram that shows when the filters and interceptors are executed during a request from a client to a server:

As we can see, the filters are always executed first, and the interceptors are executed right before calling the appropriate message body reader or writer.

If we take a look at the filters and interceptors that we've created, they will be executed in the following order:

  1. RequestClientFilter
  2. RequestClientWriterInterceptor
  3. PrematchingRequestFilter
  4. RestrictedOperationsRequestFilter
  5. RequestServerReaderInterceptor
  6. ResponseServerFilter
  7. ResponseClientFilter

Furthermore, when we have several filters or interceptors, we can specify the exact executing order by annotating them with the @Priority annotation.

The priority is specified with an Integer and sorts the filters and interceptors in ascending order for the requests and in descending order for the responses.

Let's add a priority to our RestrictedOperationsRequestFilter:

@Provider @Priority(Priorities.AUTHORIZATION) public class RestrictedOperationsRequestFilter implements ContainerRequestFilter { // ... }

Notice that we've used a predefined priority for authorization purposes.

6. Name Binding

The filters and interceptors that we've seen so far are called global because they're executed for every request and response.

However, they can also be defined to be executed only for specific resource methods, which is called name binding.

6.1. Static Binding

One way to do the name binding is statically by creating a particular annotation that will be used in the desired resource. This annotation has to include the @NameBinding meta-annotation.

Let's create one in our application:

@NameBinding @Retention(RetentionPolicy.RUNTIME) public @interface HelloBinding { }

After that, we can annotate some resources with this @HelloBinding annotation:

@GET @HelloBinding public String getHelloGreeting() { return "hello"; }

Finally, we're going to annotate one of our filters with this annotation too, so this filter will be executed only for requests and responses that are accessing the getHelloGreeting() method:

@Provider @Priority(Priorities.AUTHORIZATION) @HelloBinding public class RestrictedOperationsRequestFilter implements ContainerRequestFilter { // ... }

Keep in mind that our RestrictedOperationsRequestFilter won't be triggered for the rest of the resources anymore.

6.2. Dynamic Binding

Another way to do this is by using a dynamic binding, which is loaded in the configuration during startup.

Let's first add another resource to our server for this section:

@GET @Path("/hi") public String getHiGreeting() { return "hi"; }

Now, let's create a binding for this resource by implementing the DynamicFeature interface:

@Provider public class HelloDynamicBinding implements DynamicFeature { @Override public void configure(ResourceInfo resourceInfo, FeatureContext context) { if (Greetings.class.equals(resourceInfo.getResourceClass()) && resourceInfo.getResourceMethod().getName().contains("HiGreeting")) { context.register(ResponseServerFilter.class); } } }

In this case, we're associating the getHiGreeting() method to the ResponseServerFilter that we had created before.

It's important to remember that we had to delete the @Provider annotation from this filter since we're now configuring it via DynamicFeature.

Sekiranya kita tidak melakukan ini, penapis akan dijalankan dua kali: satu kali sebagai penapis global dan satu lagi masa sebagai penapis terikat pada kaedah getHiGreeting () .

7. Kesimpulannya

Dalam tutorial ini, kami memberi tumpuan untuk memahami bagaimana penapis dan pemintas berfungsi di Jersey 2 dan bagaimana kami dapat menggunakannya dalam aplikasi web.

Seperti biasa, kod sumber penuh untuk contoh boleh didapati di GitHub.