Simple rest with undertow - java

I have this code for server:
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(Handlers.path()
.addPrefixPath("/item", new ItemHandler())
)
.build();
server.start();
And handler:
private class ItemHandler implements HttpHandler {
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
exchange.getPathParameters(); // always null
//ItemModel item = new ItemModel(1);
//exchange.getResponseSender().send(mapper.writeValueAsString(item));
}
}
I want to send request /item/10 and get 10 in my parameter. How to specify path and get it?

You need a PathTemplateHandler and not a PathHandler, see:
Undertow server = Undertow.builder()
.addHttpListener(8080, "0.0.0.0")
.setHandler(Handlers.pathTemplate()
.add("/item/{itemId}", new ItemHandler())
)
.build();
server.start();
Then, in your ItemHandler:
class ItemHandler implements HttpHandler {
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
// Method 1
PathTemplateMatch pathMatch = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY);
String itemId1 = pathMatch.getParameters().get("itemId");
// Method 2
String itemId2 = exchange.getQueryParameters().get("itemId").getFirst();
}
}
The method 2 works because Undertow merges parameters in the path with the query parameters by default.
If you do not want this behavior, you can use instead:
Handlers.pathTemplate(false)
The same applies to the RoutingHandler, this is probably what you want to use eventually to handle multiple paths.
Handlers.rounting() or Handlers.routing(false)

Related

Receive parameter from URL with # symbol

I have problems reading the parameters with the following URL:
http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA&state=xyz&token_type=example&expires_in=3600
Example from: https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
I receive the request with simple implementation of com.sun.net.httpserver.HttpServer for integration test purposes.
Apparently, the HTTPServer can not handle the # symbol in the URL.
Here is my code. The System.out prints only: /oidc_test_callback
What do I need to do to read the parameter 'access_token'?
class OidcCallbackServer {
private final HttpServer server;
private final OidcCallbackHandler oidcCallbackHandler;
OidcCallbackServer(final int port) throws IOException {
this.server = HttpServer.create(new InetSocketAddress(InetAddress.getLocalHost().getHostAddress(), port), 0);
this.oidcCallbackHandler = new OidcCallbackHandler();
this.server.createContext("/oidc_test_callback", this.oidcCallbackHandler);
this.server.setExecutor(null); // creates a default executor
this.server.start();
}
private class OidcCallbackHandler implements HttpHandler {
#Override
public void handle(final HttpExchange request) throws IOException {
URI requestURI = request.getRequestURI();
System.out.println(requestURI);
}
}
Here is my URL for the Keycloak Server:
https://keycloak:8443/auth/realms/testRealm/protocol/openid-connect/auth?client_id=my_client_id&redirect_uri=http%3A%2F%2F192.168.202.105%3A50022%2Foidc_test_callback&response_type=id_token&scope=openid+profile&state=vZq7QdXKXsHQ3cF8hczQ4cUgPNjMjfqij-cgI7pIv4E&nonce=wHzXl08I49_OzYkA5lJkn0ZEitWZfJQFEoF12bMoK3A
...and it results in:
http://my-local-ip:50022/oidc_test_callback#state=vZq7QdXKXsHQ3cF8hczQ4cUgPNjMjfqij-cgI7pIv4E&session_state=d23b427b-99fa-4bcb-a939-b66a3e91d77d&id_token=---content-of-id-token---
When "response_type" in the first URL is "code" instead of "id_token" the URL looks like:
http://192.168.202.105:50022/oidc_test_callback?state=VgWjE0IxIc2JV3iZ14KzLsXGeBPtvKeJURnNL2yE9FA&session_state=d23b427b-99fa-4bcb-a939-b66a3e91d77d&code=4cc3a01a-a0d1-482c-8bb6-163a4d7fe287.d23b427b-99fa-4bcb-a939-b66a3e91d77d.f0001f26-ff85-47c6-befa-76f97a68ad02
SO there is no "#" symbol but the common "?".

How to test WireMockServer in JUnit5?

I am trying to write a mini-library for testing to mock common external services such as E-mail, SFTP, Buckets, HTTP APIs.
At the moment, I got stuck on WireMockServer. In WireMock docs it states that I can create both server and client to verify API calls.
I wrote the class:
public class WireMockTestServer {
private final WireMockServer server;
public WireMockTestServer(String address, MappingBuilder mappingBuilder) {
server = new WireMockServer(wireMockConfig().dynamicPort().dynamicHttpsPort());
}
public WireMockTestServer(int httpPort, int httpsPort, String address, MappingBuilder mappingBuilder) {
server = setup(
new WireMockServer(wireMockConfig().port(httpPort).httpsPort(httpsPort).bindAddress(address)),
mappingBuilder
);
}
private WireMockServer setup(WireMockServer server, MappingBuilder mappingBuilder) {
server.stubFor(mappingBuilder);
return server;
}
public void start() {
server.start();
}
public void stop() {
server.stop();
}
}
which I can path endpoint declaration and redirect my services toward it.
When I am trying to test it:
public class WireMockTestServerTest {
#Test
public void testSetup() throws Exception {
MappingBuilder mappingBuilder = get(urlEqualTo("/health"))
.willReturn(aResponse().withHeader("Content-Type", "application/json")
.withStatus(200));
WireMockTestServer server = new WireMockTestServer(8888, 9988, "127.0.0.1", mappingBuilder);
server.start();
// This line should fail
verify(getRequestedFor(urlEqualTo("/health")).withHeader("Content-Type", equalTo("text/xml")));
server.stop();
}
}
The test fails. The issue is, it fails not because of an assertion but because it starts on a wrong port 8080 which is occupied by other processes.
How can I start WireMockServer on another port and test it with JUnit 5?
I am using Java 8, Maven, Spring Boot.
As mentioned in comment static verify method tries to verify against default wiremock instance. Since you are creating a standalone instance in your test you should verify against it. Create a verify method in your WireMockTestServer :
public void verify(final RequestPatternBuilder requestPatternBuilder) {
server.verify(requestPatternBuilder);
}
and then you can verify against it :
#Test
public void testSetup() throws Exception {
MappingBuilder mappingBuilder = get(urlEqualTo("/health"))
.willReturn(aResponse().withHeader("Content-Type", "application/json")
.withStatus(200));
WireMockTestServer server = new WireMockTestServer(8888, 9988, "127.0.0.1", mappingBuilder);
server.start();
// This line should fail
server.verify(getRequestedFor(urlEqualTo("/health")).withHeader("Content-Type", equalTo("text/xml")));
server.stop();
}

Feign Client Error Handling - Suppress the Error/Exception and convert to 200 success response

I am using feign client to connect to downstream service.
I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.
I am looking for a best way of doing this.
We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.
You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:
public class ClientWrapper extends ApacheHttpClient {
private ApacheHttpClient delegate;
public ClientWrapper(ApacheHttpClient client) {
this.client = client;
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
/* execute the request on the delegate */
Response response = this.client.execute(request, options);
/* check the response code and change */
if (response.status() == 400) {
response = Response.builder(response).status(200).build();
}
return response;
}
}
This customized client can be used on any Feign client you need.
Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using #RestControllerAdvice )
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
ResponseData responseData;
ObjectMapper mapper = new ObjectMapper();
try {
responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
} catch (Exception e) {
responseData = new ResponseData();
}
return new PartialSuccessException(responseData);
}
return FeignException.errorStatus(methodKey, response);
}}
And the Exception handler as below
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(PartialSuccessException.class)
public ResponseData handlePartialSuccessException(
PartialSuccessException ex) {
return ex.getResponseData();
}
}
Change the microservice response:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(
final SSLSocketFactory sslContextFactory, final HostnameVerifier
hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(final Request request, final Request.Options
options) throws IOException {
Response response = super.execute(request, options);
if (HttpStatus.SC_OK != response.status()) {
response =
Response.builder()
.status(HttpStatus.SC_OK)
.body(InputStream.nullInputStream(), 0)
.headers(response.headers())
.request(response.request())
.build();
}
return response;
}
}
Add a Feign Client Config:
#Configuration
public class FeignClientConfig {
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}

java.lang.illegalstateexception: closed by using WSRequest : Play Java

I have created a WSClient according to the Play Documentation. And using that client object I use WSRequest to get my response. But all I'm getting is a null body and 0 as the server response code. And when I debug to where i request get() it says java.lang.illegalstateexception: closed.
Following is my code.
WS Client
private WSClient wsClient() throws IOException {
akka.stream.Materializer materializer = akka.stream.ActorMaterializer.create(akka.actor.ActorSystem.create());
// Set up the client config (you can also use a parser here):
scala.Option<String> noneString = scala.None$.empty();
WSClientConfig wsClientConfig = new WSClientConfig(
Duration.apply(120000, TimeUnit.SECONDS), // connectionTimeout
Duration.apply(120000, TimeUnit.SECONDS), // idleTimeout
Duration.apply(120000, TimeUnit.SECONDS), // requestTimeout
true, // followRedirects
true, // useProxyProperties
noneString, // userAgent
true, // compressionEnabled / enforced
SSLConfigFactory.defaultConfig());
AhcWSClientConfig clientConfig = AhcWSClientConfigFactory.forClientConfig(wsClientConfig);
// Add underlying asynchttpclient options to WSClient
AhcConfigBuilder builder = new AhcConfigBuilder(clientConfig);
DefaultAsyncHttpClientConfig.Builder ahcBuilder = builder.configure();
AsyncHttpClientConfig.AdditionalChannelInitializer logging = new AsyncHttpClientConfig.AdditionalChannelInitializer() {
#Override
public void initChannel(io.netty.channel.Channel channel) throws IOException {
channel.pipeline().addFirst("log", new io.netty.handler.logging.LoggingHandler("debug"));
}
};
ahcBuilder.setHttpAdditionalChannelInitializer(logging);
WSClient customWSClient = new play.libs.ws.ahc.AhcWSClient(ahcBuilder.build(), materializer);
customWSClient.close();
return customWSClient;
}
Request Handler
Future future = executorService.submit(new Runnable() {
#Override
public void run() {
WSRequest wsRequest = wsClient.url(url);
WSRequest complexRequest = wsRequest.setHeader(header.getKey(), header.getValue())
.setRequestTimeout(120000).setMethod(requestMethod);
CompletionStage<WSResponse> responsePromise = complexRequest.get();
CompletionStage<Result> promiseResult = responsePromise.thenApplyAsync(responseAfter -> {
int responseStatus = responseAfter.getStatus();
String body = responseAfter.getBody();
restResponse.setBody(body);
restResponse.setStatus(responseStatus);
return ok();
});
}
});
future.get();
executorService.shutdown();
I also use ExecutorService for asynchronous handling.
I searched everywhere for this problem and I still haven't found any solution for it.
Error in Debug
Debug Error
New Debug Error
Not Completed Error
The problem here is that the WSClient is being closed with customWSClient.close(). After this it is not possible to make requests.

sending request to the newly created jetty server for testing purposes

I'm writing integration JUnit test. My task is to test whether the response of my local server is correct. The mentioned server takes as a GET parameter an address of page to be analysed (for example: localhost:8000/test?url=http://www.example.com).
To avoid being dependent on www.example.com I want to start for this particular test my own jetty server, which always serves the same content.
private static class MockPageHandler extends AbstractHandler {
public void handle(String target,Request baseRequest, HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
final String responseString = loadResource("index.html");
response.getWriter().write(responseString);
baseRequest.setHandled(true);
}
}
public void test() throws Exception {
final int PORT = 8080;
final Server server = new Server(PORT);
server.setHandler(new MockPageHandler());
server.start();
final ContentResponse response =
client.newRequest("http://localhost:8000/test?url=http://localhost:8080").send();
/* some assertions. */
server.stop();
server.join();
}
Every time I execute this test, the handle method in MockPageHandler is never invoked.
Do you have any suggestions why this not works?
P.S. When I remove server.stop() and in browser type http://localhost:8080 the proper page is shown.
Quick answer:
Remove the server.join() line. That line makes the junit thread wait until the server thread stops. Which is not needed for unit testing.
Long answer:
What we (the jetty developers) have learned about using jetty embedded servers with junit.
Use the #Before and #After annotations to start and stop the server if you have 1 test method, or some requirement that the server be pristine between test methods.
Example #Before / #After (Jetty 9.x):
public class MyTest
{
private Server server;
private URI serverUri;
#Before
public void startServer() throws Exception
{
this.server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0); // let connector pick an unused port #
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Serve capture servlet
context.addServlet(new ServletHolder(new MyServlet()),"/my/*");
// Start Server
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
this.serverUri = new URI(String.format("http://%s:%d/",host,port));
}
#After
public void stopServer()
{
try
{
server.stop();
}
catch (Exception e)
{
e.printStackTrace(System.err);
}
}
#Test
public void testMe()
{
// Issue request to server
URI requestUri = serverUri.resolve("/my/test");
// assert the response
}
}
This technique makes the server start on port 0, which is a magic number that tells the underlying stack to pick an empty port and start listening. The test case then asks the server what port number it is listening on and builds out the serverUri field to be appropropriate for this test run.
This technique works great, however, it will start/stop the server for each method.
Enter, the better technique, use the #BeforeClass and #AfterClass annotations to start/stop the server once for the entire test class, running all of the methods inside of the test class against this started server.
Example #BeforeClass / #AfterClass (Jetty 9.x):
public class MyTest
{
private static Server server;
private static URI serverUri;
#BeforeClass
public static void startServer() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0); // let connector pick an unused port #
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
// Serve capture servlet
context.addServlet(new ServletHolder(new MyServlet()),"/my/*");
// Start Server
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("http://%s:%d/",host,port));
}
#AfterClass
public static void stopServer()
{
try
{
server.stop();
}
catch (Exception e)
{
e.printStackTrace(System.err);
}
}
#Test
public void testMe()
{
// Issue request to server
URI requestUri = serverUri.resolve("/my/test");
// assert the response
}
}
Doesn't look much different? Yes, the changes are subtle. #Before became #BeforeClass, #After became #AfterClass. The start/stop methods are now static. The server and serverUri fields are now static.
This technique is used where we have dozens of test methods that access the same server, and those requests do not alter the state in the server. This speeds up the test case execution by simply not recreating the server between each test method.
Give a try to "com.jayway.restassured" for your http test. too easy to write some test :
#Test
public void testNotGetAll() {
expect().
statusCode(404).
when().
get(baseUrl+"/games/");
}
this method call "http://mywebserver.local:8080/rest/games/" and verify that a 404 http status code is returned.
And this approach synchronised with a Jetty server (for example) started at pre-integration-test in the maven lifecycle, you match the perfect mix to process integration test !

Categories