I am using Spring Boot to integrate to a third party which can provide gzipped responses and I want to log the size of the compressed response body vs the decompressed so we can audit for billing etc.
I'm using RestTemplate.exchange to make the GET request and the response is coming back correctly however I am unable to retrieve the Content-Length header in the response.
I know that the third party are sending it back because I can see it when I make the same request using Curl:
HTTP/1.1 200 OK
Date: Mon, 04 Feb 2019 15:40:10 GMT
Server: Apache
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 558
Connection: close
Content-Type: application/json
This is my Java code making the request and logging the lengths:
final ResponseEntity<String> responseEntity = restTemplate.exchange(url, GET, entity, String.class);
final String body = responseEntity.getBody();
LOG.info("Payload sizes: compressed={}, uncompressed={}", responseEntity.getHeaders().getContentLength(), body.length());
For the request headers I am setting 'Accept-Encoding: gzip,deflate' as per the documentation for the third party. However when I get the response the call to getContentLength() just returns -1. I've done a lot of searching around on this and tried using a ClientHttpRequestInterceptor but no joy.
Anybody ever had the same issues?
Just in case anyone got same issue as I had, if you are using TestRestTemplate, it will not show Content-Encode when gzip, try use RestTemplate instead
Is there a way to send OData batch requests using either ODataQueryBuilder or VDM generated classes?
Example:
Let's say I'm using the Northwind public OData service (https://services.odata.org/V2/Northwind/Northwind.svc/)
Due to network performance I want to query the Customers, Categories and Regions in one only request, like this:
URL: https://services.odata.org/V2/Northwind/Northwind.svc/$batch
--batch_8008-578f-c3c1
Content-Type: application/http
Content-Transfer-Encoding: binary
GET Customers HTTP/1.1
Accept: application/json
Accept-Language: en
DataServiceVersion: 2.0
MaxDataServiceVersion: 2.0
--batch_8008-578f-c3c1
Content-Type: application/http
Content-Transfer-Encoding: binary
GET Categories HTTP/1.1
Accept: application/json
Accept-Language: en
DataServiceVersion: 2.0
MaxDataServiceVersion: 2.0
--batch_8008-578f-c3c1
Content-Type: application/http
Content-Transfer-Encoding: binary
GET Regions HTTP/1.1
Accept: application/json
Accept-Language: en
DataServiceVersion: 2.0
MaxDataServiceVersion: 2.0
--batch_8008-578f-c3c1--
(I tested this in Postman, it works.)
I have other scenarios where this would be really useful, such as reading several individual entries at once, sending ChangeSets to change several entries in a "transaction", and calling function imports.
This feature is not yet supported but, of course, completely valid. We will update this question once an update regarding this matter is available.
I'm implementing the automation of project creation in Sonarqube 7.1.
When using the endpoint /api/projects/create , I add the project successfully and it returns 200.
POST /api/projects/create HTTP/1.1
Host: localhost:9000
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 4f1da833-85fa-74c4-90a3-ae0054003199
project=0001_proj_0002&name=Project+Test+2
Next I then use the endpoint /api/permissions/add_user to add the permission to a user, it returns
204 as expected, but does not properly update the project.
POST /api/permissions/add_user HTTP/1.1
Host: localhost:9000
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 93508e26-1dd7-9ef8-c1b4-96a064e76e27
projectKey=0001_proj_0002&login=login_client%40gmail.com&permission=admin
If between these two endpoin, I sleep for 5 seconds, it works correctly.
Am I doing something wrong?
Has anyone ever experienced this?
I would like to extend my monolithic setup of jHipster with a second front-end application which accesses the same API from a different URL. As a first step, I've enabled CORS in the application.yml and I'm sending the request from the front-end with the withCredentials flag. I'm using sessions and no JWT authentication.
Many methods work now as expected, but not all. The pre-flight (OPTIONS request) always goes through and works as expected. The response of this call contains the correct CORS headers.
The actual request (e.g. the POST request to sign in), however, requires also a header (Access-Control-Allow-Origin) in the response. This header is automatically set on my custom REST interfaces, but it is not set on jHipster-generated methods like /api/authentication or /api/logout. It does also not work on Spring-Security-protected resources like /api/account (only if not logged in, 401, afterwards it works as expected with the correct headers)
As for the logout, for example, Google Chrome reacts with the following message in the console, even though the call goes through in the Network tab (POST response status 200):
XMLHttpRequest cannot load http://localhost:8080/api/logout. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access.
I was wondering, what I'm doing wrong here. I guess the headers are not properly set. I could now manually add the header (e.g. in the AjaxAuthenticationSuccessHandler), but that does not seem right.
I'm using the rather outdated version of jHipster 3.7.0. I would, however, prefer not to update the core project.
Do you have any idea, what could be causing this issue?
Headers
Here are the complete headers of the POST call to /api/logout. The OPTIONS call works as expected but in the POST response the Access-Control-Allow-Origin header is missing:
OPTIONS Request
OPTIONS /api/logout HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:9000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Access-Control-Request-Headers: x-csrf-token
Accept: */*
DNT: 1
Referer: http://localhost:9000/
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,de-CH;q=0.2,it;q=0.2
OPTIONS Response
HTTP/1.1 200 OK
Access-Control-Allow-Headers: x-csrf-token
Date: Mon, 11 Sep 2017 13:54:57 GMT
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost:9000
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Length: 0
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Access-Control-Max-Age: 1800
POST Request
POST /api/logout HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 0
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Origin: http://localhost:9000
X-CSRF-TOKEN: [***token removed in this snippet***]
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
DNT: 1
Referer: http://localhost:9000/
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,de-CH;q=0.2,it;q=0.2
Cookie: [***removed cookies in this snippet***]
POST Response
HTTP/1.1 200 OK
Expires: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Set-Cookie: CSRF-TOKEN=null; path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT
Set-Cookie: JSESSIONID=[***removed jsessionID in this snippet***]; path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT
Set-Cookie: remember-me=null; path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT
X-XSS-Protection: 1; mode=block
Pragma: no-cache
Date: Mon, 11 Sep 2017 13:54:57 GMT
Connection: keep-alive
X-Content-Type-Options: nosniff
Content-Length: 0
Reproduction
You can reproduce this behavior by using this demo project of jHipster in version 3.7.0. Then, enable the CORS settings (all of them) in src/main/resources/application.yml. After that, create a new user on localhost:8080 and activate it. Finally, try to authenticate with the following JS snippet from another port (e.g. a simple node server or xampp). You can also try to make a simple POST call to /api/account, which will lead to a 401 error. See the Google Chrome console for the error message.
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.2/axios.js"></script>
<script type="text/javascript">
var Http = axios.create({
baseURL: 'http://localhost:8080/api',
});
Http.interceptors.request.use(function (config) {
config.xsrfCookieName = 'CSRF-TOKEN';
config.xsrfHeaderName = 'X-CSRF-TOKEN';
config.withCredentials = true;
return config;
});
var credentials = {
username: 'test-user',
password: 'test123',
rememberMe: true
};
Http.post('authentication', 'j_username=' + credentials.username +
'&j_password=' + credentials.password +
'&remember-me=' + credentials.rememberMe +
'&submit=Login');
</script>
It looks like you are running into this CORS issue where one of the filters before the CorsFilter in Spring Security's filter chain throws an error. The request never reaches the CorsFilter, causing the CORS headers to be missing in the response. That's why Chrome complains about the missing headers in the console even though it's a different error.
You need to put the CorsFilter before CsrfFilter and UsernamePasswordAuthenticationFilter so that if there's an issue with either of those filters, the response still gets the CORS headers. To accomplish this, add the following code to SecurityConfiguration.java:
// import CorsFilter
import org.springframework.web.filter.CorsFilter;
...
...
// inject for JHipster v3 apps, add to constructor for JHipster v4 apps
#Inject
private CorsFilter corsFilter;
...
...
// add this line in the configure method before ".exceptionHandling()"
.addFilterBefore(corsFilter, CsrfFilter.class)
Also in SecurityConfiguration.java, you can set #EnableWebSecurity(debug = true) to see the filter chain for each request. You can verify everything is correct by making sure the CorsFilter is before the CsrfFilter and UsernamePasswordAuthenticationFilter in the chain:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter <--------- Before CsrfFilter and UsernamePasswordAuthenticationFilter
CsrfFilter
CsrfCookieGeneratorFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
If you are running into CSRF issues in your new client, you may need to make a GET request after a POST to refresh the CSRF token. An example of how JHipster handles this can be seen when logging out.
Have a look at how the org.springframework.web.cors.CorsConfiguration Spring bean is configured in the JHipster common application properties. I think you are missing the following configuration line:
exposed-headers: "Authorization"
I just documented this a few hours ago, and also added this configuration by default a few hours ago.
In the next release, which should be out tomorrow, you should have a new page called Separating the front-end and the API server that should explain that better - and if you have found a better solution, don't hesitate to improve that page, it's a first version.
I'm coding a small api in node.js (express) for my spine.js application.
Getting and creating new objects in the database is working pretty well. But on deleting/editing I've git a problem right now.
Every object gets an individual id created by the JS app.
In the database every entry has a unique key, too.
When I call object.destroy() I can see the ajax request which is sent to my server. But as parameter for the id I always have the id which was given by the app.
But for deleting/updating stuff in the database I need the key which is stored in the database (mongoDB in my case)
So how can I send the right id to the server or identify the right entry on the server side!?
Why not send the correct id back when creating your model like shown in the Spine documentation.
After a new Page record has been created, Spine sends a POST request
to /pages containing the following:
POST /pages HTTP/1.1 Host: spine-rails3.herokuapp.com Accept:
application/json, text/javascript, */*; q=0.01 Content-Type:
application/json; charset=UTF-8 X-Requested-With: XMLHttpRequest
Content-Length: 65
{"name":"Dummy page","id":"EEAF4B17-5F1D-4C06-B535-D9B58D84142F"}
Then the server should respond with something like this:
HTTP/1.1 201 Created Content-Type: application/json; charset=utf-8
Location: http://spine-rails3.herokuapp.com/pages/196 Content-Length:
28 Connection: keep-alive
{"name":"Dummy page","id":1}