Skip navigation

RESTEasy

5 Posts authored by: Weinan Li

The UndertowJaxrsSpringServer

 

#undertow #spring

 

Recently I’m working on migrating resteasy-spring tests to use the Undertow container. Before these tests were using the TJWS embedded container.

 

As resteasy-tjws container is deprecated in master, so there is some work to be done to make undertow container to load the resteasy-spring components correctly.

 

In order to achieve the goal, I created a new module called resteasy-undertow-spring, and it contains a single class called UndertowJaxrsSpringServer.

 

This server class will accept the spring xml configuration file, and load the configured spring context beans into spring provided org.springframework.web.servlet.DispatcherServlet. Here is the relative code in UndertowJaxrsSpringServer:

 

 

In addition, the resteasy-spring module contains a default xml configuration file called springmvc-resteasy.xml, and it can be used by default to setup the resteasy-spring component properly.

 

So users just need to prepare their own spring config file and include the springmvc-resteasy.xml to load the resteasy-spring module. Here is a minimal example of the config:

 

 

In above config, we can see it includes the springmvc-resteasy.xml provided by resteasy-spring module, and it will scan the org.jboss.resteasy.springmvc.test package to include the user-written jaxrs resources.

 

And here is the code to setup the UndertowJaxrsSpringServer and start it:

 

 

The above code is part of the tests in resteasy-undertow-spring, and the tests can be used as usage example:

 

Resteasy/BasicSpringTest.java at master · resteasy/Resteasy · GitHub

 

And the tests shows more advanced spring configurations that can be used as reference.

We have removed resteasy-jettison-provider from master branch, and here is the tracker bug on this change:

 

https://issues.jboss.org/browse/RESTEASY-1316

 

The major affect is that the resteasy-link module is now dependent on resteasy-jackson2-provider to replace resteasy-jettison-provider to support JAXB annotation -> JSON data marshaling.

 

In addition, Jackson2 has some subtle differences on supporting JAXB annotations comparing with the default JAXB or Jettison marshallers. So some resteasy-link test classes are adjusted to accommodate this change. Here is the relative PR that shows the difference after the migration:

 

https://github.com/resteasy/Resteasy/pull/1850/files#diff-a873f7dcbc6d4b04d29b4f0dd1015f76R17

 

The above change shows the changes in JAXB annotations after changing the JAXB -> JSON provider to resteasy-jackson2-provider for resteasy-link module.

 

Above is the summary of Jettison removal.

# RESTEasy WADL Grammar Support

RESTEasy has added WADL grammar support by this PR:

 

 

- [RESTEASY-1695 Add GRAMMARS into RESTEasy WADL by liweinan · Pull Request #1649 · resteasy/Resteasy · GitHub](https://github.com/resteasy/Resteasy/pull/1649)

 

 

The major change is that `ResteasyWadlGrammar` is added into `ResteasyWadlWriter`:

 

 

 

In addition, the `ResteasyWadlWriter` is rewritten now, and all the static methods are now instance methods. It means users need to create an instance of `ResteasyWadlWriter` and put it into per-deployment scope.

 

 

To avoid people to write the boilerplate code, the `ResteasyWadlDefaultResource` can be used for deployment, and it can be put into the `getSingleton()` method of `Application` class:

 

 

```java

@Provider

@ApplicationPath("/")

public class WadlTestApplication extends Application {

   public static Set<Class<?>> classes = new HashSet<Class<?>>();

   public static Set<Object> singletons = new HashSet<Object>();

...

   @Override

   public Set<Object> getSingletons() {

   ...

         ResteasyWadlDefaultResource defaultResource = new ResteasyWadlDefaultResource();

         singletons.add(defaultResource);

      }

      return singletons;

   }

}

```

 

 

And then the URL `/application.xml` is enabled by the methods inside `ResteasyWadlDefaultResource`.

 

 

To enable the WADL Grammar support, users need to create an instance of `ResteasyWadlGrammar` and put it into the instance of `ResteasyWadlWriter`.

 

 

The recommended way is to use it with `ResteasyWadlDefaultResource`. Here is an example:

 

 

```java

ResteasyWadlDefaultResource defaultResource = new ResteasyWadlDefaultResource();

 

 

ResteasyWadlWriter.ResteasyWadlGrammar wadlGrammar = new ResteasyWadlWriter.ResteasyWadlGrammar();

wadlGrammar.enableSchemaGeneration();

 

 

defaultResource.getWadlWriter().setWadlGrammar(wadlGrammar);

```

 

 

As the code shown above, we have created an instance of `ResteasyWadlGrammar`, and it’s injected into the `ResteasyWadlWriter` instance included by `ResteasyWadlDefaultResource` instance.

 

 

In addition, we have called the `wadlGrammar.enableSchemaGeneration()` method, and it will scan the resources classes, and generate grammar files for JAXB annotated classes. Suppose we have entity class like this:

 

 

```java

import javax.xml.bind.annotation.XmlElement;

import javax.xml.bind.annotation.XmlRootElement;

import java.util.List;

 

 

@XmlRootElement(name = "listType")

public class ListType {

 

 

    private List<String> values;

 

 

    @XmlElement

    public List<String> getValues() {

        return values;

    }

 

 

    public void setValues(List<String> values) {

        this.values = values;

    }

}

```

 

 

And it’s used in resource class:

 

 

```java

import javax.ws.rs.Consumes;

import javax.ws.rs.POST;

import javax.ws.rs.Path;

 

 

@Path("/extended")

public class ExtendedResource {

 

 

    @POST

    @Consumes({"application/xml"})

    public String post(ListType income) {

        return "foo";

    }

}

```

 

 

And if we enable the grammar generation as shown above, then we will get sample output from `/application.xml` like this:

 

 

```xml

$ http http://localhost:8081/application.xml

HTTP/1.1 200 OK

Content-type: application/xml;charset=UTF-8

Date: Wed, 31 Oct 2018 07:57:38 GMT

Transfer-encoding: chunked

 

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<application xmlns="http://wadl.dev.java.net/2009/02">

    <grammars>

        <include href="xsd0.xsd">

            <doc title="Generated" xml:lang="en"/>

        </include>

    </grammars>

</application>

```

 

 

The above output shows that a grammar file is genrated, and it is called `xsd0.xsd`. The instance of `ResteasyWadlDefaultResource` will prepare a URL link named `/wadl-extended`, and it will serve the generated grammar file. Here is the example:

 

 

```xml

$ http http://localhost:8081/wadl-extended/xsd0.xsd

HTTP/1.1 200 OK

Content-type: application/xml;charset=UTF-8

Date: Wed, 31 Oct 2018 07:58:53 GMT

Transfer-encoding: chunked

 

 

<?xml version="1.0" standalone="yes"?>

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

 

 

  <xs:element name="listType" type="listType"/>

 

 

  <xs:complexType name="listType">

    <xs:sequence>

      <xs:element name="values" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>

    </xs:sequence>

  </xs:complexType>

</xs:schema>

```

 

 

As shown in the above example, the grammar is generated for `ListType` entity class. If you don’t want RESTEasy to generate the grammar file for you, you can use the `includeGrammars()` method provided by the instance of `ResteasyWadlGrammar`. Here is an example:

 

 

```java

ResteasyWadlWriter.ResteasyWadlGrammar wadlGrammar = new ResteasyWadlWriter.ResteasyWadlGrammar();

wadlGrammar.includeGrammars(“application-grammars.xml”);

...

```

 

 

The `application-grammars.xml` file is grammar descriptor file provided by yourself, and it should be put into your project’s classpath. Here is a sample of `application-grammars.xml`:

 

 

```xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<grammars xmlns="http://wadl.dev.java.net/2009/02"

    xmlns:xsd="http://www.w3.org/2001/XMLSchema"

    xmlns:xi="http://www.w3.org/1999/XML/xinclude">

    <include href="schema.xsd" />

</grammars>

```

 

 

From above file we can see `schema.xsd` is the included schema file, and it should also be provided by yourself. Here is a sample of `schema.xsd`:

 

 

```xml

<?xml version=“1.0” encoding=“UTF-8” standalone=“yes”?>

<xs:schema version=“1.0” xmlns:xs=“http://www.w3.org/2001/XMLSchema”>

 

 

    <xs:element name=“listType” type=“listType”/>

 

 

    <xs:complexType name=“listType”>

        <xs:sequence>

            <xs:element name=“values” type=“xs:string” minOccurs=“0” maxOccurs=“unbounded”/>

        </xs:sequence>

    </xs:complexType>

</xs:schema>

```

 

 

And if you have called `wadlGrammar.includeGrammars(“application-grammars.xml”)`, then you will get the included section in `/application.xml`:

 

 

```xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<application xmlns="http://wadl.dev.java.net/2009/02">

    <grammars>

        <include href="schema.xsd"/>

    </grammars>

...

</application>

```

 

 

As the example shown above, the `schema.xsd` is included, and it can be fetched by using `/wadl-extended/schema.xsd` if you have registered the instance of `ResteasyWadlDefaultResource` into your `Application` and setup `ResteasyWadlGrammar` properly:

 

 

```xml

$ http http://localhost:8081/wadl-extended/schema.xsd

HTTP/1.1 200 OK

Content-type: application/xml;charset=UTF-8

Date: Wed, 31 Oct 2018 08:12:56 GMT

Transfer-encoding: chunked

 

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

 

 

    <xs:element name="listType" type="listType"/>

 

 

    <xs:complexType name="listType">

        <xs:sequence>

            <xs:element name="values" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>

        </xs:sequence>

    </xs:complexType>

</xs:schema>

```

 

 

Above is the description about the RESTEasy WADL Grammar feature.

 

 

The other change is that `ResteasyWadlServlet` and `ResteasyWadlServletWriter` is now deprecated, because it doesn’t support the grammar feature, and these two classes will be removed from master branch in the future. Using `ResteasyWadlDefaultResource` is recommended to enable the WADL feature.

Previously, the RESTEasy tracing feature just support the pre-formatted, text-based information like this:

 

$ curl -i http://localhost:8081/foo | head
...
X-RESTEasy-Tracing-000: START       [ ---- /  ---- ms |  ---- %] baseUri=[http://localhost:8081/] requestUri=[http://localhost:8081/foo] method=[GET] authScheme=[n/a] accept=*/* accept-encoding=n/a accept-charset=n/a accept-language=n/a content-type=n/a content-length=n/a
X-RESTEasy-Tracing-001: START       [ ---- /  0.60 ms |  ---- %] Other request headers: Accept=[*/*] Host=[localhost:8081] User-Agent=[curl/7.55.1]
1X-RESTEasy-Tracing-002: PRE-MATCH   [ 0.01 /  2.54 ms |  0.00 %] PreMatchRequest summary: 0 filters
0X-RESTEasy-Tracing-003: REQ-FILTER  [ 0.02 /  5.35 ms |  0.00 %] Filter by [io.weli.tracing.HttpMethodOverride @4b8ca04a]
0X-RESTEasy-Tracing-004: REQ-FILTER  [ 0.03 /  5.66 ms |  0.00 %] Filter by [org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter @4d5e22c1]
X-RESTEasy-Tracing-005: REQ-FILTER  [ 0.73 /  5.88 ms |  0.00 %] Request summary: 2 filters
  X-RESTEasy-Tracing-006: MATCH       [ ---- /  6.44 ms |  ---- %] Matched resource: template=[[org.jboss.resteasy.core.registry.ClassExpression @7e528471]] regexp=[\Q\E(.*)] matches=[[org.jboss.resteasy.core.registry.SegmentNode @5072e501]] from=[]
  X-RESTEasy-Tracing-007: MATCH       [ ---- /  6.60 ms |  ---- %] Matching path [/foo]
...

 

This kind of tracing info is easy for the human to read, but it’s hard for program to process. Especially when it’s used in a distributed environemnt when the tracing info need to be passed across applications.

 

So now we add the feature to let tracing info to be returned in JSON format. To use this feature, you need to add the tracing-api as dependency firstly:

 

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-tracing-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

 

As shown above, now the tracing-api is a standalone project and it belongs to the resteasy-extensions project:

 


https://github.com/resteasy/resteasy-extensions

 

After including the above basic package, then we need to choose a JSON provider for tracing module to generate JSON formatted info.

 

There are two JSON providers you can choose from and they both support the tracing feature.

 

One is the jackson2 provider:

 

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jackson2-provider</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>

 

The other option is to use the json-binding provider:

 

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-json-binding-provider</artifactId>
    <version>4.0.0-SNAPSHOT</version>
</dependency>

 

After including either of the above module, now we can request the resource server for JSON formatted data. The method is to send a format request header:

 

X-RESTEasy-Tracing-Accept-Format: JSON

 

Here is a request example:

 

$ curl -H "X-RESTEasy-Tracing-Accept-Format: JSON" -i http://localhost:8081/foo

 

And the response will be like this:

 

X-RESTEasy-Tracing-000:
[{"duration":0,"event":"START","text":"baseUri=[http://localhost:8081/]
requestUri=[http://localhost:8081/foo] method=[GET] authScheme=[n/a] accept=*/*
accept-encoding=n/a accept-charset=n/a accept-language=n/a content-type=n/a
content-length=n/a
","timestamp":46600869878437},{"duration":0,"event":"START_HEADERS","text":"Other
request headers: Accept=[*/*] Host=[localhost:8081] User-Agent=[curl/7.55.1]
X-RESTEasy-Tracing-Accept-Format=[JSON]
","timestamp":46600870751617},{"duration":18336,"event":"PRE_MATCH_SUMMARY","tex
t":"PreMatchRequest summary: 0
filters","timestamp":46600872781491},{"duration":20724,"event":"REQUEST_FILTER",
"text":"Filter by [io.weli.tracing.HttpMethodOverride
@585b0020]","timestamp":46600876716467},{"duration":19414,"event":"REQUEST_FILTE
R","text":"Filter by [org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter
@3779d352]","timestamp":46600877017341},{"duration":657192,"event":"REQUEST_FILT
ER_SUMMARY","text":"Request summary: 2 filters",
...

 

After reformat the above response, we can see the response structure is like this:

 

[
{
  “duration”: 0,
  “event”: “START”,
  “text”: “baseUri=[http://localhost:8081/] requestUri=[http://localhost:8081/foo] method=[GET] authScheme=[n/a] accept=*/* accept-encoding=n/a accept-charset=n/a accept-language=n/a content-type=n/a content-length=n/a “,
  “timestamp”: 46600869878437
},
{
  “duration”: 0,
  “event”: “START_HEADERS”,
  “text”: “Other request headers: Accept=[*/*] Host=[localhost:8081] User-Agent=[curl/7.55.1] X-RESTEasy-Tracing-Accept-Format=[JSON] “,
  “timestamp”: 46600870751617
},
{
  “duration”: 18336,
  “event”: “PRE_MATCH_SUMMARY”,
  “text”: “PreMatchRequest summary: 0 filters”,
  “timestamp”: 46600872781491
},
{
  “duration”: 20724,
  “event”: “REQUEST_FILTER”,
  “text”: “Filter by [io.weli.tracing.HttpMethodOverride @585b0020]”,
  “timestamp”: 46600876716467
},
...
]

 

The timestamp is the event start time, and the other fields are quite straightforward.

 

The JSON formatted data should be more suitable to be parsed by code.

 

This feature is currently just in 4.0.0-SNAPSHOT, and haven’t been officially released yet.

Tracing feature is a way for the users of the RESTEasy to understand what's going on internally in the container when a request is processed. It's different from the pure logging system or profiling feature, which provide more general information about the request/response info, etc.

 

 

On the other hand, the tracing feature provides more internal states of the JAX-RS container. For example, it could be able to show what filters a request is going through, or how long time a request is processed, etc.

 

Introduction to the design of tracing feature

Currently it doesn't have a standard or spec to define the tracing feature, so the tracing feature is tightly coupled with the concrete JAX-RS implementation itself.

 

 

The RESTEasy tracing feature supports three working modes:

 

- OFF
- ON_DEMAND
- ALL

 

ALL will enable the tracing feature. ON_DEMAND mode will give the control to client side: A client can send a tracing request via HTTP header and get the tracing info back from response headers. OFF mode will disable the tracing feature, and this is the default mode.

 

 

On the other aspect, the tracing feature has different tracing logging levels. Here is the list of the levels:

 

- SUMMARY
- TRACE
- VERBOSE

 

The SUMMARY level will emit some brief tracing information. The TRACE level will produce more detailed tracing information, and the VERBOSE level will generate extremely detailed tracing information. Because there are no specs on these tracing levels yet, so the level of the tracing info is currently defined by RESTEasy internally.

 

 

The tracing feature uses the JBoss Logging framework to output the trace log, so the jboss logger configuration controls the final output of the tracing info. If you enable the tracing feature but disable the jboss logger output, you still can't get the tracing info you want. In addition, the tracing logging levels are mapped to jboss logger log levels, which means the jboss logger controls is the actual place to control the tracing level threshold.

 

Examples of using tracing feature

 

By default, the tracing feature is turned off. If you want to enable the tracing feature, you can set the tracing mode and tracing level via the context-param parameters in your web project’s web.xml file. Here is an example of the setting:

 

  resteasy.server.tracing.type
  ALL


  resteasy.server.tracing.threshold
  SUMMARY

 

With above setting, we have enabled the server tracing, and put the tracing level to summary. If the underlying jboss logger’s output threadshold is higher than the tracing level setting, then the users can start to get the tracing info from server side and from response headers.

 

 

Here is some sample text of the server side tracing log:

 

16:06:40,794 INFO  [general] PRE_MATCH_SUMMARY PreMatchRequest
summary: 0 filters [ 0.03 ms]
16:06:40,797 DEBUG [general] REQUEST_FILTER Filter by
[io.weli.tracing.HttpMethodOverride @50d53072] [ 0.09 ms]
16:06:40,797 DEBUG [general] REQUEST_FILTER Filter by
[org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter @7e6bde58] [
0.03 ms]
16:06:40,798 INFO  [general] REQUEST_FILTER_SUMMARY Request summary: 2
filters [ 1.24 ms]
16:06:40,804 DEBUG [general] REQUEST_FILTER Filter by
[org.jboss.resteasy.plugins.providers.sse.SseEventSinkInterceptor
@27930ef8 #2147483647] [ 0.50 ms]
16:06:40,804 INFO  [general] REQUEST_FILTER_SUMMARY Request summary: 1
filters [ 0.93 ms]
16:06:40,813 INFO  [general] METHOD_INVOKE Resource [SINGLETON|class
io.weli.tracing.TracingConfigResource|io.weli.tracing.TracingConfigResource@7a1234bf]
method=[public java.lang.String
io.weli.tracing.TracingConfigResource.type(org.jboss.resteasy.spi.ResteasyDeployment)]
[10.67 ms]
16:06:40,813 DEBUG [general] DISPATCH_RESPONSE Response:
[org.jboss.resteasy.specimpl.BuiltResponse @28a0b6dc
<200/SUCCESSFUL|OK|java.lang.String @52a345f7>] [ ---- ms]
16:06:40,814 INFO  [general] FINISHED Response status: 200 [ ---- ms]
16:06:40,827 DEBUG [general] RESPONSE_FILTER Filter by
[org.jboss.resteasy.plugins.interceptors.MessageSanitizerContainerResponseFilter
@35bb27cd #4000] [ 0.02 ms]
16:06:40,832 INFO  [general] RESPONSE_FILTER_SUMMARY Response summary:
2782639920301360 filters [2782639925.90 ms]

 

 

 

For client side, here is some sample text in response header:

 

16:06:40,938 FINE  [headers] http-outgoing-0 << HTTP/1.1 200 OK
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-008: FINISHED    [ ---- /  3.16 ms |  ---- %]
Response status: 200
16:06:40,939 FINE  [headers] http-outgoing-0 << Date: Fri, 08 Jun 2018
08:06:40 GMT
16:06:40,939 FINE  [headers] http-outgoing-0 << Connection: keep-alive
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-000: PRE-MATCH   [ 0.00 /  0.00 ms |  0.12 %]
PreMatchRequest summary: 0 filters
16:06:40,939 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-001: REQ-FILTER  [ 0.01 /  0.33 ms |  0.23 %]
Filter by [io.weli.tracing.HttpMethodOverride @50d53072]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-002: REQ-FILTER  [ 0.01 /  0.60 ms |  0.18 %]
Filter by [org.jboss.resteasy.core.AcceptHeaderByFileSuffixFilter
@7e6bde58]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-003: REQ-FILTER  [ 0.54 /  0.85 ms | 17.03 %]
Request summary: 2 filters
16:06:40,940 FINE  [headers] http-outgoing-0 << Content-Type:
application/octet-stream
16:06:40,940 FINE  [headers] http-outgoing-0 << Content-Length: 48
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-004: REQ-FILTER  [ 0.01 /  1.47 ms |  0.28 %]
Filter by [org.jboss.resteasy.plugins.providers.sse.SseEventSinkInterceptor
@27930ef8 #2147483647]
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-005: REQ-FILTER  [ 0.26 /  1.71 ms |  8.34 %]
Request summary: 1 filters
16:06:40,940 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-006: INVOKE      [ 0.76 /  2.23 ms | 24.08 %]
Resource [SINGLETON|class
io.weli.tracing.TracingConfigResource|io.weli.tracing.TracingConfigResource@7a1234bf]
method=[public java.lang.String
io.weli.tracing.TracingConfigResource.logger(org.jboss.resteasy.spi.HttpRequest)
throws java.lang.NoSuchMethodException]
16:06:40,941 FINE  [headers] http-outgoing-0 <<
X-RESTEasy-Tracing-007: INVOKE      [ ---- /  2.96 ms |  ---- %]
Response: [org.jboss.resteasy.specimpl.BuiltResponse @7534fda1
<200/SUCCESSFUL|OK|java.lang.String @7bf31d77>]

 

 

Above is some sample tracing text. Currently the tracing feature is still under development, and more tracing info entries will be provided to the users. And the formal document will be provided as the development is going on.

 

If you’d like to have a look at the tracing feature in action, you can current see the simple test case in RESTEasy master branch:

 

https://github.com/resteasy/Resteasy/blob/master/testsuite/integration-tests/src/test/java/org/jboss/resteasy/test/tracing/BasicTracingTest.java

 

In above is the brief description of the RESTEasy document.