Efficient way to handle post requests on Java Vert.x? - java

This is how I am currently handling post requests on my vert.x server:
router.post("/test").handler(context -> context.request().bodyHandler(body -> {
try {
JsonObject jsonObject = new JsonObject(body.toString());
...
} catch(Exception e) { }
}));
I am sending test requests using Postman where the body has data as "raw - application/json".
This works. But, is this the right way?
I also tried sending the data as parameters in "form-data" but I am not able to get the parameters. The following prints out the entire request, I can see the data, but cannot parse it to a json or map.
router.post("/test").handler(context ->
context.request().bodyHandler(System.out::println));
Any help is appreciated. Thanks.

There are many ways you can program you request handlers.
You can find different approaches in this documentation https://vertx.io/docs/vertx-web/java/
Here is an approach I prefer when writing my handlers.
package org.api.services.test;
import org.api.services.test.CustomDTO;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
public class TestApi extends AbstractVerticle {
#Override
public void start(Future<Void> startFuture) throws Exception {
super.start(startFuture);
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
//register a router for post request that accepts only requests with */json MIME type on exact path /test.
router.post("/test/").consumes("*/json").handler(this::testHandler);
...
}
private void testHandler(RoutingContext routingContext) {
//recommended way to extract json
JsonObject jsonObject = routingContext.getBodyAsJson();
//automatically map json to custom object
CustomDTO customDTO = Json.decodeValue(routingContext.getBodyAsString(), CustomDTO.class);
...
}
}
If you are sending request containing form-data you can extract 2 ways:
If you add router.route().handler(BodyHandler.create()); than all form attributes will be merged as request parameters.
By default, the body handler will merge any form attributes into the request parameters. If you don’t want this behaviour you can use disable it with setMergeFormAttributes.
You can extract them by using routingContext.request().getParam("attribute_name")
If you are not using any BodyHandler you need to set routingContext.request().setExpectMultipart(true); and than access the form attributes like this routingContext.request().formAttributes()

If you need "form-data", you should add "BodyHandler" before your handle.
final Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
....
context.request().getParam("id")

Related

Passing WebSocket parameters to a programmatical endpoint

My front-end code looks like this:
const ws = new WebSocket("wss://localhost/yeah?param1=value1&param2=value2");
My endpoint at the backend is programmatical:
class YeahEndpoint extends Endpoint {
...
#Override
public void onOpen(Session session, EndpointConfig ec) {
Map<String, String> params = session.getPathParameters(); <-- returns empty map! No param1 or param2.
}
...
}
As I was not able to find relevant information on the web, I need to ask this here: how a programmatical endpoint obtains the request parameters?
I simply should have used session.getQueryString() instead of session.getPathParameters().
If you are using the Jetty as the back-end, look at the following code:
session.getUpgradeRequest().getParameter("param")

How do I run a java program on a server with Spark?

I'm trying to automate a call so that when a user calls a Twilio number, the code will generate XML and send it as an HTTP response to the caller. The example on their webpage goes:
#SuppressWarnings("serial")
#WebServlet("/voice")
public class IncomingCallServlet extends HttpServlet {
// Handle HTTP POST to /voice
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Create a TwiML builder object
VoiceResponse twiml = new VoiceResponse.Builder()
.say(new Say.Builder("Hello world!")
.voice(Say.Voice.ALICE)
.build())
.build();
// Render TwiML as XML
response.setContentType("text/xml");
try {
response.getWriter().print(twiml.toXml());
} catch (TwiMLException e) {
e.printStackTrace();
}
}
}
But how do I get this to run since there's no main method? I'm using spark to run it on a local port then creating a webhook to the application using ngrok. It works if I have a main, but the example here doesn't give any.
Any suggestion on how I'd get this code to run and generate the XML.
Funny thing is, I don't see any reference to Spark in your code, and it could run on any Java Web container, provided that you declare the servlet in a well formed web.xml. If I understand your question and code extract correctly, you seem to be willing to rely upon the Jetty server embedded into Spark to load this servlet.
If you want to leverage Spark and avoid the hassle of explicitly declaring your servlet, you could write something like this (assuming you're running Java 8):
import com.twilio.twiml.Say;
import com.twilio.twiml.VoiceResponse;
import static spark.Spark.*
public class IncomingCall {
public static void main(String[] args) {
// You might want to pass the listen port
// e.g as CLI argument or system property
port(4567);
post("/voice", (request, response) -> {
// Create a TwiML builder object
VoiceResponse twiml = new VoiceResponse.Builder()
.say(new Say.Builder("Hello world!")
.voice(Say.Voice.ALICE)
.build())
.build();
// Render TwiML as XML
response.type("text/xml");
try {
return twiml.toXml();
} catch (TwiMLException e) {
// This will result in a HTTP 500
throw new RuntimeException(e);
}
}
}
}
It's possible to implement SparkApplication interface, declare a filter in your web.xml and run it in another web server according to the documentation.

Adding Header To A SOAP Call (Java equavalent in C#)

The SOAP API I am intending to use has given a working example in Java. In every request to the API one should add three values to the header (I just guess they are a domain, a password and api key). To this aim we override the org.apache.axis.client.Stub like this:
public class SeveraApiStubBase extends org.apache.axis.client.Stub {
#Override
public org.apache.axis.client.Call _createCall() throws ServiceException {
org.apache.axis.client.Call _call = super._createCall();
_call.addHeader(new org.apache.axis.message.SOAPHeaderElement(
"http://something.somethingelse.com/", "WebServicePassword", "API_KEY"));
return _call;
}
}
And then we run the method with the provided header.
I was wondering what the equivalent is in C#.
Update: The use of the IClientMessageInspector class
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
{
httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
Normally you would use WCF if using C# rather than Axis. It is a little different of an approach than Axis.
Assuming you're making a client consuming an existing WSDL, you would start by using svcutil to generate your service contract code. Here is a link that describes this part. The example they give is a service with primitive inputs to all operations, so it doesn't show generation of complex type classes.
You can then use message inspectors to intercept the outgoing request and add a header. The IClientMessageInspector interface has the method BeforeSendRequest that passes a Message class as an argument. The Message class has a Headers collection where you can add whatever headers you need.

Getting the actual class of a message body

I'm passing a List of different objects to a camel route. I would like the route to split the body into one object per message and put the class of the body in a header (using a processor).
from("direct:in")
.split(body())
.process(new JmsTypeHeaderProcessor(body().getClass().getName()))
.to("mock:out");
I'm trying it like this...
#Produce(uri = "direct:in") private ProducerTemplate template;
#EndpointInject(uri = "mock:out") private MockEndpoint endpoint;
#Test
public void testRoute() throws Exception {
List<Object> list = new ArrayList<>();
list.add("String");
list.add(Integer.valueOf(1));
list.add(Boolean.FALSE);
template.sendBody(list);
for (Exchange ex : endpoint.getExchanges()) {
System.out.println("JMSType=" + ex.getIn().getHeader("JMSType"));
}
}
When I run that I find I actually have the headers
JMSType=org.apache.camel.builder.ValueBuilder
JMSType=org.apache.camel.builder.ValueBuilder
JMSType=org.apache.camel.builder.ValueBuilder
whereas I expected, and would like
JMSType=java.lang.String
JMSType=java.lang.Integer
JMSType=java.lang.Boolean
What is needed to get the class of the actual body?
BTW. I can see that log("body.class") returns what I want but I have not been able to follow how it works or adapt it for my needs.
The Camel routes are designed in the route builder and the code is run once, to setup the routes.
So this code
.process(new JmsTypeHeaderProcessor(body().getClass().getName()))
Is invoked once, and body().getClass() returns the ValueBuilder as that is what is used at design time in the DSL to specify body etc.
If you want to access the runtime message body, then get that from the Exchange from the process method of your processor. That is the runtime message and then you can get the body.

How can I override the decisions made during JAX-RS Content Negotiation?

I'm using RESTEasy 2.2.1.GA as my JAX-RS implementation to create a client to connect to a third party service provider. (Education.com's REST API if it matters)
To make sure I haven't missed an important implementation detail here are code samples:
Service Interface
#Path("/")
public interface SchoolSearch {
#GET
#Produces({MediaType.APPLICATION_XML})
Collection<SchoolType> getSchoolsByZipCode(#QueryParam("postalcode") int postalCode);
}
Calling Class
public class SimpleSchoolSearch {
public static final String SITE_URL = "http://api.education.com/service/service.php?f=schoolSearch&key=****&sn=sf&v=4";
SchoolSearch service = ProxyFactory.create(SchoolSearch.class, SITE_URL);
public Collection<SchoolType> getSchools() throws Exception {
Collection<SchoolType> schools = new ArrayList<SchoolType>();
Collection<SchoolType> response = service.getSchoolsByZipCode(35803);
schools.addAll(response);
return schools;
}
}
After setting up tests to make this call, I execute and see the following exception being thrown.
org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException: Unable to find JAXBContext for media type: text/html;charset="UTF-8"
From reading the RESTEasy/JAX-RS documentation, as I understand it, when the response is returned to the client, prior to the unmarshaling of the data, a determination is made (Content Negotiation??) about which mechanism to use for unmarshalling. (I think we're talking about a MessageBodyReader here but I'm unsure.) From looking at the body of the response, I see that what is returned is properly formatted XML, but the content negotiation (via HTTP header content-type is indeed text/html;charset ="UTF-8") is not allowing the text to be parsed by JAXB.
I think that the implementation is behaving correctly, and it is the service that is in error, however, I don't control the service, but would still like to consume it.
So that being said:
Am I correct in my understanding of why the exception is thrown?
How do I work around it?
Is there a simple one line annotation that can force JAXB to unmarshal the data, or will I need to implement a custom MessageBodyReader? (If that is even the correct class to implement).
Thanks!
Follow Up:
I just wanted to post the few changes I made to Eiden's answer. I created a ClientExecutionInterceptor using his code and the information available at Resteasy ClientExecutionInterceptor documentation. My final class looks like
#Provider
#ClientInterceptor
public class SimpleInterceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse response = ctx.proceed();
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
The big difference is the addition of the #Provider and #ClientExecutionInterceptor annotations. This should insure that the interceptor is properly registered.
Also, just for completeness, I registered the Interceptor slightly differently for my tests. I used:
providerFactory.registerProvider(SimpleInterceptor.class);
I'm sure there are several solutions to this problem, but I can only think of one.
Try so set the content-type using a ClientExecutionInterceptor:
public class Interceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse<?> execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse<?> response = ctx.proceed();
response
.getHeaders()
.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
public void getSchools() throws Exception {
ResteasyProviderFactory.getInstance()
.getClientExecutionInterceptorRegistry()
.register( new Interceptor() );
SchoolSearch service =
ProxyFactory.create(SchoolSearch.class, SITE_URL);
}
I dont know about any such annotation, others might do, but a workaround is to create a local proxy. Create a controller, that passes all parameters to education.com using a
java.Net.URL.get()
return the answer that you received, but modify the header. Then connect your client to the local proxy controller.

Categories

Resources