I am sending a POST request using the following code but the request is send in the form of chunked (Transfer-Encoding: chunked). I googled about the problem and it says to include the Content-Length but in the below code I could not figure out how could I set the Content-Length:
#RequestMapping(value = "/contacts", method = RequestMethod.POST)
public Map<String, ContactInfo> addContactInfo(
#RequestBody Map<String, ContactInfo> ContactInfoDto) {
ContactInfo contactInfo = ContactInfoDto.get("contact");
if (contactInfo == null) {
throw new IllegalArgumentException("Contact not found.");
}
contactInfo = this.contactInfoManager.addNew(contactInfo);
Map<String, ContactInfo> map = new HashMap<>();
map.put("contact", contactInfo);
return map;
}
You can use ResponseEntity to set headers explicitly. The tricky bit is figuring out how long your content actually is:
#RequestMapping(value = "/contacts", method = RequestMethod.POST)
public ResponseEntity<Map<String, ContactInfo>> addContactInfo(#RequestBody Map<String, ContactInfo> contactInfoDto) throws JsonProcessingException {
ContactInfo contactInfo = contactInfoDto.get("contact");
if (contactInfo == null) {
throw new IllegalArgumentException("Contact not found.");
}
contactInfo = this.contactInfoManager.addNew(contactInfo);
Map<String, ContactInfo> map = new HashMap<>();
map.put("contact", contactInfo);
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_LENGTH, String.valueOf(new ObjectMapper().writeValueAsString(map).length()));
return new ResponseEntity<Map<String, ContactInfo>>(map, headers, HttpStatus.CREATED);
}
Test:
$ curl -v http://localhost:8080/contacts/ -X POST -d '{ "contact": { "name": "foo" } }' -H 'Content-Type: application/json' && echo
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /contacts/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 32
>
* upload completely sent off: 32 out of 32 bytes
< HTTP/1.1 201 Created
< Server: Apache-Coyote/1.1
< X-Application-Context: application
< Content-Type: application/json;charset=UTF-8
< Content-Length: 26
< Date: Fri, 10 Jun 2016 13:24:23 GMT
<
* Connection #0 to host localhost left intact
{"contact":{"name":"foo"}}
The following code:
#RequestMapping(value = "/contacts", method = RequestMethod.POST)
public Map<String, ContactInfo> addContactInfo(
#RequestBody Map<String, ContactInfo> ContactInfoDto,
#RequestHeader(value = HttpHeaders.CONTENT_LENGTH, required = true) Long contentLength
) { ... }
Can be used to require Content-Length header to be sent.
Just note that you also have to add that header in code that sends request (most of the clients do that automatically but better check)
Related
I have a JAX-RS REST resource which should be able to respond in different charsets as indicated by the client's preference through the Accept-Charset header.
However, as JAX-RS seems to ignore the Accept-Charset header by default, I wrote two methods explicitly stating the two different charsets I want to support:
#GET
#Path("test")
#Produces("text/plain; charset=UTF-8")
public String test_utf8() {
return "Hello World";
}
#GET
#Path("test")
#Produces("text/plain; charset=cp1047")
public String test_cp1047() {
return "Hello World";
}
However, when now calling the method using curl:
curl -v -H "Accept-Charset: cp1047;q=1.0, *;q=0" "http://localhost:8080/rest/test" -H "Accept: text/plain"
The server responds in UTF-8:
> GET /rest/test HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.3
> Accept-Charset: cp1047;q=1.0, *;q=0
> Accept: text/plain
>
< HTTP/1.1 200 OK
< Connection: keep-alive
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 19
< Date: Mon, 06 Jul 2020 21:29:11 GMT
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
Hello World
In addition, a log message appears in the server log:
23:29:11,356 WARN [org.jboss.resteasy.resteasy_jaxrs.i18n] (default
task-2) RESTEASY002142: Multiple resource methods match request "GET
/test". Selecting one. Matching methods: [public java.lang.String
org.example.RestResource.test_utf8(), public java.lang.String
org.example.RestResource.test_cp1047()]
How can I force the server to honor the charset requested by the client?
Solved it by creating a custom ContainerResponseFilter which parses the Accept-Charset header and uses the "best" charset as requested by the client:
#Provider
#Priority(Priorities.HEADER_DECORATOR)
#HonorAcceptCharset
public class HonorAcceptCharsetFilter implements ContainerResponseFilter {
private static final Pattern AcceptedEncodingPattern = Pattern.compile("([A-Za-z*0-9_\\-]+)(?:; *[qQ] *= *([0-9]+(?:\\.[0-9]+)?))?");
public static final String AcceptCharsetHeaderName = "Accept-Charset";
public static final String ContentTypeHeaderName = "Content-Type";
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
MediaType mediaType = responseContext.getMediaType();
if (mediaType == null)
return;
Charset charset = this.determineCharset(requestContext);
if (charset == null)
return;
responseContext.getHeaders().putSingle(ContentTypeHeaderName, mediaType.withCharset(charset.name()));
}
private Charset determineCharset(ContainerRequestContext requestContext) {
String acceptCharset = requestContext.getHeaderString(AcceptCharsetHeaderName);
if (acceptCharset == null)
return null;
List<Map.Entry<Charset, Double>> acceptedCharsets = Arrays.stream(acceptCharset.split(", *"))
.map(this::parseAcceptedEncoding)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (acceptCharset.length() == 0)
return null;
if (acceptCharset.length() == 1)
return acceptedCharsets.get(0).getKey();
OptionalDouble maxQuality = acceptedCharsets.stream().mapToDouble(Map.Entry::getValue).max();
List<Charset> candidates = acceptedCharsets.stream().filter(it -> it.getValue() == maxQuality.getAsDouble()).map(Map.Entry::getKey).collect(Collectors.toList());
if (candidates.size() == 1)
return candidates.get(0);
if (candidates.stream().anyMatch(it -> it.name().toLowerCase().matches("utf-?8")))
return StandardCharsets.UTF_8;
return candidates.get(0);
}
private Map.Entry<Charset, Double> parseAcceptedEncoding(String acceptedEncoding) {
Matcher matcher = AcceptedEncodingPattern.matcher(acceptedEncoding);
if (!matcher.find())
return null;
String charsetName = matcher.group(1);
if ("*".equals(charsetName))
return null;
try {
if (!Charset.isSupported(charsetName))
return null;
} catch (IllegalCharsetNameException ex) {
return null;
}
Charset charset = Charset.forName(charsetName);
String quality = matcher.group(2);
double qualityNumber = StringUtils.isAllEmpty(quality) ? 1.0 : Double.parseDouble(quality);
return new AbstractMap.SimpleEntry<>(charset, qualityNumber);
}
}
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
public #interface HonorAcceptCharset {
}
Now I just need one method which I can decorate with #HonorAcceptCharset in order to enable the filter (I don't want to enable it globally as I do not want to add charsets to binary types. Maybe the filter could look into the media type and determine if it wants to activate depending on a predefined list as an alternative.
Still strikes me somewhat strange that this kind of behaviour is not enabled by default.
I have a rest API implemented in Java (MSF4J codegen from swagger) and a swagger 2 definition that describes it.
A swagger UI is hosted on a web server. The API is deployed on a VM somewhere on the internet.
My Problem is that the "try it out" function of the swagger UI doesn't work. I always get a "401 Unauthorized". When I take the curl command from the UI and paste it into my terminal it works.
Last week I didn't have HTTPS or Basic Authentication - just HTTP - and it worked fine. Now I don't know why it doesn't work.
Since I changed the swagger definition to https the UI makes an OPTIONS request. I implemented that, but I get 401 responses.
The certificate comes from Lets Encrypt and is used by an apache web server. The apache is a proxy to the rest api on the same machine.
Here is my authentication interceptor:
public class BasicAuthSecurityInterceptor extends AbstractBasicAuthSecurityInterceptor {
#Override
protected boolean authenticate(String username, String password) {
if (checkCredentials(username, password))
return true;
return false;
}
private boolean checkCredentials(String username, String password) {
if (username.equals("testuser"))
return BCrypt.checkpw(password, "$2a$10$iXRsLgkJg3ZZGy4utrdNyunHcamiL2RmrKHKyJAoV4kHVGhFv.d6G");
return false;
}
}
Here is a part of the api:
public abstract class DeviceApiService {
private static final Logger LOGGER = LogManager.getLogger();
public abstract Response deviceGet() throws NotFoundException;
public abstract Response deviceIdAvailableLoadGet(Integer id, Long from, Long to, String resolution)
throws NotFoundException;
public abstract Response deviceIdGet(Integer id) throws NotFoundException;
protected Response getOptionsResponse() {
String allowedOrigin = "";
try {
allowedOrigin = PropertyFileHandler.getInstance().getPropertyValueFromKey("api.cors.allowed");
} catch (IllegalArgumentException | PropertyException | IOException e) {
LOGGER.error("Could not get allowed origin.", e);
}
Response response = Response.ok().header("Allow", "GET").header("Access-Control-Allow-Origin", allowedOrigin)
.header("Access-Control-Allow-Headers", "authorization, content-type").build();
return response;
}
}
public class DeviceApi {
private final DeviceApiService delegate = DeviceApiServiceFactory.getDeviceApi();
// #formatter:off
#GET
#Produces({ "application/json" })
#io.swagger.annotations.ApiOperation(
value = "Get devices",
notes = "",
response = Device.class,
responseContainer = "List",
authorizations = { #io.swagger.annotations.Authorization(value = "basicAuth") },
tags = { "Device", }
)
#io.swagger.annotations.ApiResponses(
value = { #io.swagger.annotations.ApiResponse(
code = 200,
message = "200 OK",
response = Device.class,
responseContainer = "List")
})
public Response deviceGet() throws NotFoundException {
return delegate.deviceGet();
}
#OPTIONS
#Consumes({ "application/json" })
#Produces({ "application/json" })
#io.swagger.annotations.ApiOperation(value = "CORS support", notes = "", response = Void.class, authorizations = {
#io.swagger.annotations.Authorization(value = "basicAuth") }, tags = { "Device", })
#io.swagger.annotations.ApiResponses(value = {
#io.swagger.annotations.ApiResponse(code = 200, message = "Default response for CORS method", response = Void.class) })
public Response deviceOptions() throws NotFoundException {
return delegate.getOptionsResponse();
}
}
EDIT:
This are the headers of the request the swagger ui creates:
Accept: text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: de,en-US;q=0.7,en;q=0.3
Access-Control-Request-Headers: authorization
Access-Control-Request-Method: GET
Connection: keep-alive
DNT: 1
Host: api.myfancyurl.com
Origin: http://apidoc.myfancyurl.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/61.0
It seems that the authorization header is missing. When I edit the request and resend it with the authorization header and encoded credentials it works.
But I don't know why swagger doesn't add this header. Should one accept all options requests without authorization?
I try to make a restfull web service with Jersey. I would like to give a link in the response of that request :
GET /mac/ws/gtm HTTP/1.1
Host: localhost:8080
Accept: application/json
Cache-Control: no-cache
I want the response to be :
HTTP/1.1 200 OK
link: </dossiers>;rel=dossiers
{
"message": "Hello"
}
But the response is :
HTTP/1.1 200 OK
{
"message": "Hello"
}
The link is not produce !
Look my Gtm Resource :
#Component
#Path("/gtm")
public class GTmRessource
{
#GET
#Produces(MediaType.APPLICATION_JSON)
public GTm getJson()
{
GTm gtm = new GTm();
return gtm;
}
}
And my Gtm entity
#XmlRootElement()
#Link(value = #Ref(value = "/dossiers", method = "get"), rel = "dossiers")
public class GTm
{
String message = "Hello";
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
}
What's wrong ?
Thanks for help.
By
See Declarative Hyperlinking: Configuration
You need to add the LinkFilter either programmatically:
resourceConfig.getContainerResponseFilters().add(LinkFilter.class);
or through web.xml
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.sun.jersey.server.linking.LinkFilter</param-value>
</init-param>
C:\>curl -i http://localhost:8080/gtm
HTTP/1.1 200 OK
Content-Type: application/json
Link: </dossiers>;rel=dossiers
Date: Thu, 04 Dec 2014 12:38:06 GMT
Transfer-Encoding: chunked
{"message":"Hello"}
i'm trying to make a RESTful WS to upload a file with CXFRS camel component, i'm trying to retrive the uploaded file via getAttachment method, but it is always empty.
This is my code:
EndPoint class: ExposedApi.java
#Path("/test")
public class ExposedApi {
#POST
#Path("/resources/solver")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
public Response upload(#Multipart(value = "file") Attachment attachments ){
return null;
}
}
Code that extends routebuolder: RouteConf.java
public class RouteConf extends RouteBuilder {
#Override
public void configure() throws Exception {
// TODO Auto-generated method stub
getContext().setTracing(true);
from("cxfrs://http://localhost:9090/test?resourceClasses=org.foo.ExposedApi")
.streamCaching()
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
Message inMessage = exchange.getIn();
String operationName = inMessage.getHeader(CxfConstants.OPERATION_NAME, String.class);
if (operationName=="upload"){
Map<String, DataHandler> names= inMessage.getAttachments();
exchange.getOut().setBody(inMessage.getAttachmentNames().toString()+ " TEST "+ names.keySet().toString());
}
}
});
}
}
Curl request:
curl -v -F "file=#/Users/Massimo/Desktop/ic_eb.png" http://localhost:9090/test/test/resources/solver
and this is the response
* Adding handle: conn: 0x7f92cb804000
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7f92cb804000) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 9090 (#0)
* Trying ::1...
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9090 (#0)
> POST /test/test/resources/solver HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:9090
> Accept: */*
> Content-Length: 89280
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=----------------------------837830fae872
>
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Fri, 10 Oct 2014 14:54:51 GMT
< Content-Length: 10
* Server Jetty(7.6.9.v20130131) is not blacklisted
< Server: Jetty(7.6.9.v20130131)
<
* Connection #0 to host localhost left intact
[] TEST []
What am i doing wrong? how i can retrive the attached file?
after a bit of work i've find the solution.
the attachment is inside the body.
If you want retrive the attachment this line of code worked for me
Attachment att = (Attachment)inMessage.getBody(ArrayList.class).get(0);
I generate proxy classes with wsdl.exe to request web-services, that are probably build at java platform. The problem is with encoding of response. I get '?' instead of russian letters.(for example '????26' instead of 'АН26')
I also use soapUI and everything works well there. I am not experienced at configuring .Net clients. So how I could determine and configure proper encoding for response. I already played with app.config as next:
I attach headers information here. I don't wee encoding info at responce headers...
request headers:
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "urn:#DCSSci_ListFlight_5"
Content-Length: 641
Host: 109.73.1.66:23022
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
response headers:
HTTP/1.1 200 OK
Date: Thu, 06 Sep 2012 03:47:52 GMT
Server: Apache/2.2.10 (Linux/SUSE)
200 OKX-FidelXML-Version: 2.0
Content-length: 15464
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/xml
Solution:
public class TraceExtension : SoapExtension
{
Stream oldStream;
Stream newStream;
public override Stream ChainStream(Stream stream)
{
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}
public override object GetInitializer(Type WebServiceType)
{
return null;
}
public override void Initialize(object initializer)
{
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
newStream.Position = 0;
Copy(newStream, oldStream);
break;
case SoapMessageStage.BeforeDeserialize:
message.ContentType = "application/soap+xml; utf-8";
Copy(oldStream, newStream);
newStream.Position = 0;
break;
case SoapMessageStage.AfterDeserialize:
break;
}
}
void Copy(Stream from, Stream to)
{
TextReader reader = new StreamReader(from, System.Text.Encoding.GetEncoding("utf-8"));
TextWriter writer = new StreamWriter(to, System.Text.Encoding.GetEncoding("utf-8"));
writer.WriteLine(reader.ReadToEnd());
writer.Flush();
}
}
[AttributeUsage(AttributeTargets.Method)]
public class TraceExtensionAttribute : SoapExtensionAttribute
{
private int priority;
public override Type ExtensionType
{
get { return typeof(TraceExtension); }
}
public override int Priority
{
get { return priority; }
set { priority = value; }
}
}
And than just add
[TraceExtension()]
attribute for proxy invoke method
You can override GetWebResponse of your proxy and change the encoding
public class YourProxyClass : SoapHttpClientProtocol
{
protected override WebResponse GetWebResponse(WebRequest request)
{
var response = base.GetWebResponse(request);
response.Headers["Content-Type"] = "text/xml; charset=utf-8"; //<==
return response;
}
}