I want to have two interceptors for every request I do on a webservice. One for the outgoing communication, and one for the response.
I am using ClientHttpRequestInterceptor which is working for the outgoing. I am setting it as follows:
//Rest template
RestTemplate tpl = api.getRestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add( new OutgoingRequestInterceptor() );
tpl.setInterceptors( interceptors );
However, I want something like this interceptor for the incoming (response). I checked Spring Framework sourcecode and I couldn't find anything for this.
Any tips?
Edit:
Maybe I am confused or something is wrong in my head. Im a bit ill today.
I've the following code in my interceptor class:
#Override
public ClientHttpResponse intercept( HttpRequest request, byte[] bytes, ClientHttpRequestExecution requestExecution ) throws IOException
{
SLog.d( "intercepted!!"+request.getURI()+". Bytes: "+bytes );
try
{
Thread.sleep( 5000 );
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
ClientHttpResponse response = requestExecution.execute( request, bytes );
SLog.d( "Response Headers: " + response.getHeaders());
return response;
}
Question: Is this code working for outgoing, incoming, or both?
Because the log:request.getUri() is returning the destination URL.
Then, on the Response object, I get the headers sent by WebService.
So what I am sure of, is that response is actually the server response. But... How about getUri() thingy? Is it triggered before actually sending the request, or after?
Okay. After some tricky debugging, I got it.
Even though the interceptor class is called ClientHttpRequestInterceptor, it's intercepting both. Request from client, and respose from Server.
This interceptor class is something like a wrapper.
So...
This method is the wrapper of the whole request. From BEFORE request and after the request is done.
This part of the code is triggered BEFORE request is sent to webservice
This part of the code ACTUALLY CONTACTS WEBSERVICE, so it "pauses" there until it gets the response from the web service.
We return the response generated. Notice that if you use method response.getBody() which is an InputStream, you will consume it, so it will be null afterwards. I say that because you CAN'T directly log it. You've to mirror it first.
Related
When using the java.net.http.HttpClient classes in Java 11 and later, how does one tell the client to follow through an HTTP 303 to get to the redirected page?
Here is an example. Wikipedia provides a REST URL for getting the summary of a random page of their content. That URL redirects to the URL of the randomly-chosen page. When running this code, I see the 303 when calling HttpResponse#toString. But I do not know how to tell the client class to follow along to the new URL.
HttpClient client = HttpClient.newHttpClient();
HttpRequest request =
HttpRequest
.newBuilder()
.uri( URI.create( "https://en.wikipedia.org/api/rest_v1/page/random/summary" ) )
.build();
try
{
HttpResponse < String > response = client.send( request , HttpResponse.BodyHandlers.ofString() );
System.out.println( "response = " + response ); // ⬅️ We can see the `303` status code.
String body = response.body();
System.out.println( "body = " + body );
}
catch ( IOException e )
{
e.printStackTrace();
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
When run:
response = (GET https://en.wikipedia.org/api/rest_v1/page/random/summary) 303
body =
Problem
You're using HttpClient#newHttpClient(). The documentation of that method states:
Returns a new HttpClient with default settings.
Equivalent to newBuilder().build().
The default settings include: the "GET" request method, a preference of HTTP/2, a redirection policy of NEVER [emphasis added], the default proxy selector, and the default SSL context.
As emphasized, you are creating an HttpClient with a redirection policy of NEVER.
Solution
There are at least two solutions to your problem.
Automatically Follow Redirects
If you want to automatically follow redirects then you need to use HttpClient#newBuilder() (instead of #newHttpClient()) which allows you to configure the to-be-built client. Specifically, you need to call HttpClient.Builder#followRedirects(HttpClient.Redirect) with an appropriate redirect policy before building the client. For example:
HttpClient client =
HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL) // follow redirects
.build();
The different redirect policies are specified by the HttpClient.Redirect enum:
Defines the automatic redirection policy.
The automatic redirection policy is checked whenever a 3XX response code is received. If redirection does not happen automatically, then the response, containing the 3XX response code, is returned, where it can be handled manually.
There are three constants: ALWAYS, NEVER, and NORMAL. The meaning of the first two is obvious from their names. The last one, NORMAL, behaves just like ALWAYS except it won't redirect from https URLs to http URLs.
Manually Follow Redirects
As noted in the documentation of HttpClient.Redirect you could instead manually follow a redirect. I'm not well versed in HTTP and how to properly handle all responses so I won't give an example here. But I believe, at a minimum, this requires you:
Check the status code of the response.
If the code indicates a redirect, grab the new URI from the response headers.
If the new URI is relative then resolve it against the request URI.
Send a new request.
Repeat 1-4 as needed.
Obviously configuring the HttpClient to automatically follow redirects is much easier (and less error-prone), but this approach would give you more control.
Please find below code where i was calling another api from my REST APi in java.
To note I am using java version 17. This will solve error code 303.
#GetMapping(value = "url/api/url")
private String methodName() throws IOException, InterruptedException {
var url = "api/url/"; // remote api url which you want to call
System.out.println(url);
var request = HttpRequest.newBuilder().GET().uri(URI.create(url)).setHeader("access-token-key", "accessTokenValue").build();
System.out.println(request);
var client = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
System.out.println(client);
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response);
System.out.println(response.body());
return response.body();
}
I receive a Request from the Client which returns a SendRequest-Object that has a HttpMethod, a path and data to send.
Now I would like to send the Request depending on the object I get to the API.
After sending I will get a Response.
The problem now is how can I send the payload and receive the response.
#Bean
public IntegrationFlow httpPostSendRawData() {
return IntegrationFlows.from(
Http.inboundGateway("/api/data/send")
.requestMapping(r -> r.methods(HttpMethod.POST))
.statusCodeExpression(dataParser().parseExpression("T(org.springframework.http.HttpStatus).BAD_REQUEST"))
.requestPayloadType(ResolvableType.forClass(DataSend.class))
.crossOrigin(cors -> cors.origin("*"))
.headerMapper(dataHeaderMapper())
)
.channel("http.data.send.channel")
.handle("rawDataEndpoint", "send")
.transform(/* here i have some transformations*/)
.handle(Http.outboundGateway((Message<SendRequest> r)->r.getPayload().getPath())
.httpMethod(/* Here I would like to get the Method of SendRequest*/)
//add payload
.extractPayload(true))
.get();
It's not clear what is your SendRequest, but the idea for the method is exactly the same what you have done for the url:
.httpMethodFunction((Message<SendRequest> r)->r.getPayload().getMethod())
Although, since you want to have some extraction for the request body, you need to do that in advance in the transform() and move values for url and method to the headers.
There is just no any payload extraction in the Http.outboundGateway: it deals with the whole request payload as an entity for HTTP request body.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
HTTP GET with request body
I've read few discussions here which do not advocate sending content via HTTP GET. There are restrictions on the size of data that can be sent via clients (web browsers). And handling GET data also depends on servers. Please refer section Resources below.
However, I've been asked to test the possibility to send content via HTTP GET using RestTemplate. I refered few discussions on spring forum but they were not answered. (Please note sending data via http Post works fine). The discussion here suggests using POST instead.
dev env - JBoss AS 5.1, Spring 3.1.3
Client
#Test
public void testGetWithBody()
{
// acceptable media type
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
acceptableMediaTypes.add(MediaType.TEXT_PLAIN);
// header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
// body
String body = "hello world";
HttpEntity<String> entity = new HttpEntity<String>(body, headers);
Map<String, Object> uriVariables = new HashMap<String, Object>();
uriVariables.put("id", "testFile");
// Send the request as GET
ResponseEntity<String> result = restTemplate.exchange(
"http://localhost:8080/WebApp/test/{id}/body",
HttpMethod.GET, entity, String.class, uriVariables);
Assert.assertNotNull(result.getBody());
}
Server #Controller
#RequestMapping(value = "/{id}/body", method = RequestMethod.GET)
public #ResponseBody
String testGetWithBody(#PathVariable String id,
#RequestBody String bodyContent)
{
return id + bodyContent;
}
The problem -
executing this test case returns 500 Internal Server Error. On debugging, I found that the controller is not hit.
Is it correct to understand that the RestTemplate provides the way to send data as request body, but the error occurs because the server could not handle the request body ?
If the request body sent via HTTP Get is not conventional why does RestTemplate provide the APIs to allow sending it ? Does this mean there are few servers capable of handling the Request body via GET ?
Resources - discussions on sending body via HTTP GET using RestTemplate at spring forum
http://forum.springsource.org/showthread.php?129510-Message-body-with-HTTP-GET&highlight=resttemplate+http+get
http://forum.springsource.org/showthread.php?94201-GET-method-on-RestTemplate-exchange-with-a-Body&highlight=resttemplate+http+get
Resources - General discussions on sending body via HTTP GET
get-with-request-body
is-this-statement-correct-http-get-method-always-has-no-message-body
get-or-post-when-reading-request-body
http-uri-get-limit
Is it correct to understand that the RestTemplate provides the way to send data as request body, but the error occurs because the server could not handle the request body ?
You can tell by looking at network traffic (does the request get sent with a request body and a GET method?) and at server logs (the 500 result you receive must have a server-side effect that gets logged, and if not, configure the server to do so).
If the request body sent via HTTP Get is not conventional why does RestTemplate provide the APIs to allow sending it ? Does this mean there are few servers capable of handling the Request body via GET ?
Because it is a generic class that also allows you to craft requests that can include a message body.
As stated in HTTP GET with request body:
In other words, any HTTP request message is allowed to contain a message body, and thus [a server] must parse messages with that in mind. Server semantics for GET, however, are restricted such that a body, if any, has no semantic meaning to the request. The requirements on parsing are separate from the requirements on method semantics.
A body on a GET cannot do anything semantically, because you are requesting a resource. It's like you tell the server: "Give me resource X, oh, and have some apples!". The server won't care about your apples and happily serve resource X - or throw an error because it doesn't like any offers in a request.
However, I've been asked to test the possibility to send content via HTTP GET
Please tell the one who requested this that this is a case that should not have to be tested, because no sensible implementation supports it.
I want to do something to sign up users with spark+java+hibernate+postgres
This is my code:
post("/registrar", (request, response) -> {
EntityManagerFactory emf = Persistence.
createEntityManagerFactory("compradorcitoPU");
EntityManager em = emf.createEntityManager();em.getTransaction().begin();
em.persist(u);
em.getTransaction().commit();
em.close(); return null; });
but this error shows up:
INFO spark.webserver.MatcherFilter - The requested route
[/registrarnull] has not been mapped in Spark
I had a similar problem. The items I'm returning are large and I wanted to write them out over stream. So, my software looked like this:
post("/apiserver", "application/json", (request, response) -> {
log.info("Received request from " + request.raw().getRemoteAddr());
ServerHandler handler = new ServerHandler();
return handler.handleRequest(request, response);
});
In my handler, I got the raw HttpResponse object, opened its OutputStream and wrote over it like so:
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.raw().getOutputStream(), records);
Since I knew I had written over the OutputStream what the caller had asked for at that point (or an error), I figured I could just return null. My program worked fine. Spark would route the request to my handler as expected. And, since I was writing over the raw OutputStream, I was getting back what was expected on the client side. But, I kept seeing the message '/apiserver route not defined' in my server logs.
In looking at the Spark documentation, it says:
The main building block of a Spark application is a set of routes. A route is made up of three simple pieces:
A verb (get, post, put, delete, head, trace, connect, options)
A path (/hello, /users/:name)
A callback (request, response) -> { }
Obviously Spark does not know what you wrote over the raw HttpResponse and as a web-server, you should be providing some response to callers. So, if your response is null, you haven't fulfilled the requirements of providing a callback and you get the error that there's no map found even if Spark behaved as expected otherwise. Just return a response (null is not a response, "200 OK" is) and the error will go away.
[Edit] Spelling and grammar.
do not "return null" instead return the empty string or something
As explained in the comments of this issue, SparkJava considers that returning null means the route has not been mapped and therefore it logs the error message and replies a response with 404 status.
To avoid such behaviour you have to return a String (possibly empty).
The error message will disappear and a response with the String as body and 200 status will be replied.
In my case, I had to implement the options request to please the preflight CORS check:
options("/*", (request,response)->{
String accessControlRequestHeaders = request.headers("Access-Control-Request-Headers");
if (accessControlRequestHeaders != null) {
response.header("Access-Control-Allow-Headers", accessControlRequestHeaders);
}
String accessControlRequestMethod = request.headers("Access-Control-Request-Method");
if(accessControlRequestMethod != null){
response.header("Access-Control-Allow-Methods", accessControlRequestMethod);
}
return "OK";
});
Here is my java.net client code
URL server = new URL(serverUrl);
URLConnection connection = server.openConnection();
/* DoOutput flag is set to true to inform URL connection that we are
* sending data out. The default behavior is false. This will change
* the request from GET to a POST.
*/
connection.setDoOutput (true);
/* Setting the UseCaches to false allows the application "tunnel
* through" and ignore the caches.
*/
connection.setUseCaches (false);
connection.setRequestProperty ( "Content-Type", "application/octet-stream" );
ObjectOutputStream sendData = new ObjectOutputStream(connection.getOutputStream());
sendData.writeObject(packet);
sendData.flush();
sendData.close();
ObjectInputStream recieveData = new ObjectInputStream(connection.getInputStream());
response = (Serializable) recieveData.readObject();
System.out.println(response);
recieveData.close();
And this is my server code with spring to receive the request and send back response
#ResponseBody
#RequestMapping(value="/upload", method=RequestMethod.POST)
public Serializable fileUploadHandler(#RequestBody FilePacket packet, HttpServletRequest request, HttpSession session, HttpServletResponse response){
// handle request
return "response";
}
While running the code with above configuration, upon sending the request to server, I get Http error 415
java.io.IOException: Server returned HTTP response code: 415 for URL:
I removed #RequestBody and replaced it with
FilePacket packet = (FilePacket) new ObjectInputStream(request.getInputStream()).readObject();
and the request part was good. But then after a request I got the 406
java.io.IOException: Server returned HTTP response code: 415 for URL:
and so I replaced #ResponseBody with
new ObjectOutputStream(response.getOutputStream()).writeObject("response");
and then everything was good.
I've used these annotations with ajax and they work like charm but with java.net , I'm getting unsupported media type (415) and not acceptable (406). Is spring not able to read the request and send proper response or I'm missing something here? Is there a way to make it work?
Note: The class FilePacket is Serializable, one of whose fields is of type byte[] which holds the file data.
Spring uses a collection of HttpMessageConverter instances to deserialize request bodies into arguments it will pass to your handler method.
Spring does not have a such a HttpMessageConverter which converts application/octet-stream to your custom FilePacket class. You could instead use byte[] and do the conversion yourself (or provide your own HttpMessageConverter to do it).
Spring uses the same tactic for #ResponseBody, using these same HttpMessageConverter instances for serializing an object returned to the response body. It has nothing built-in which uses Java Serialization. Note that for most, if not all, of these response types, your client needs to provide an explicit Accept: header to specify what it accepts.