I´m making a Web Service, using Java and Glassfish as server.
I´m also using Apache Server for Processing HTTP requests, i.e, when I make a request, I´m able to get the standard informations, like:
HTTP/1.1 200 OK[\r][\n]
Server: GlassFish Server Open Source Edition 4.1 [\r][\n]"
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 4.1 Java/Oracle Corporation/1.8)[\r][\n]
Set-Cookie: JSESSIONID=efc5aa919b55321d3aeaf2c9b3b6; Path=/context; HttpOnly[\r][\n]
Content-Type: text/xml; charset=utf-8[\r][\n]
Date: Thu, 07 May 2015 15:26:40 GMT[\r][\n]
Transfer-Encoding: chunked[\r][\n]
WWW-Authenticate: Basic realm="file"[\r][\n]
Content-Language: [\r][\n]
Content-Type: text/html[\r][\n]
Content-Length: 1090[\r][\n]
SOAPAction: ""[\r][\n]
Host: localhost:8080[\r][\n]
Connection: Keep-Alive[\r][\n]
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)[\r][\n]
One Example of a Web Operation that I developed is:
#WebMethod(operationName = "someoperation")
#Produces(MediaType.APPLICATION_XML)
public void makeHappen(#WebParam(name = "req") Object obj,
#WebParam(name = "resp", mode = WebParam.Mode.OUT) Holder<String> response) {
To List of information that I get, I want to add own specifications, like:
--> OperationName: someOperation
HTTP/1.1 200 OK[\r][\n]
Server: GlassFish Server Open Source Edition 4.1 [\r][\n]"
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 4.1 Java/Oracle Corporation/1.8)[\r][\n]
Set-Cookie: JSESSIONID=efc5aa919b55321d3aeaf2c9b3b6; Path=/context; HttpOnly[\r][\n]
Content-Type: text/xml; charset=utf-8[\r][\n]
Date: Thu, 07 May 2015 15:26:40 GMT[\r][\n]
Transfer-Encoding: chunked[\r][\n]
WWW-Authenticate: Basic realm="file"[\r][\n]
Content-Language: [\r][\n]
Content-Type: text/html[\r][\n]
Content-Length: 1090[\r][\n]
SOAPAction: ""[\r][\n]
Host: localhost:8080[\r][\n]
Connection: Keep-Alive[\r][\n]
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)[\r][\n]
For WebSphere Application Server:
Refer to the documentation and examples in Sending transport headers with JAX-WS
Here is a short programming example that illustrates how request
transport headers are sent by a JAX-WS Web services client
application:
public class MyApplicationClass {
// Inject an instance of the service's port-type.
#WebServiceRef(EchoService.class)
private EchoPortType port;
// This method will invoke the web service operation and send transport headers on the request.
public void invokeService() {
// Set up the Map that will contain the request headers.
Map<String, Object> requestHeaders = new HashMap<String, Object>();
requestHeaders.put(“MyHeader1”, “This is a string value”);
requestHeaders.put(“MyHeader2”, new Integer(33));
requestHeaders.put(“MyHeader3”, new Boolean(true));
// Set the Map as a property on the RequestContext.
BindingProvider bp = (BindingProvider) port;
bp.getRequestContext().put(com.ibm.websphere.webservices.Constants.REQUEST_TRANSPORT_PROPERTIES, requestHeaders);
// Invoke the web services operation.
String result = port.echoString(“Hello, world!”);
}
}
Here is a short programming example that illustrates how response
transport headers are sent by a JAX-WS Web services endpoint
implementation class:
#WebService
public class EchoServiceImpl implements EchoServicePortType {
// Inject an instance of WebServiceContext so we can retrieve
// the MessageContext for each invocation of this endpoint.
#Resource
WebServiceContext ctxt;
/**
* Default constructor.
*/
public EchoServiceImpl() {
....
}
public String echoString(String input) {
String result = “Echo result: “ + input;
// Retrieve the MessageContext from the injected WebServiceContext.
MessageContext mc = ctxt.getMessageContext();
// Send some headers back in the response message.
Map<String, Object> responseHeaders = new HashMap<String, Object>();
responseHeaders.put("MyHeader1", "This is a string response value");
responseHeaders.put("MyHeader2", new Integer(33));
responseHeaders.put("MyHeader3”, new Boolean(false));
// Set the response header Map on the MessageContext.
mc.put(com.ibm.websphere.webservices.Constants.RESPONSE_TRANSPORT_PROPERTIES, responseHeaders);
return result;
}
}
For GlassFish Application Server:
You can get the javax.xml.ws.WebServiceContext and from it javax.xml.ws.handler.MessageContext. Then add to the MessageContext your headers, something like this:
...
Map<String, List<String>> headers = new HashMap<String, List<String>>();
headers.put("OperationName", someOperation);
messageContext.put(MessageContext.HTTP_REQUEST_HEADERS, headers)
...
Also you can try to append the HTTP header to the request by using this approach:
...
Dispatch<SOAPMessage> dispatch =
service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);
Map<String, List<String>> headers =
new HashMap<String, List<String>>();
headers.put("OperationName", someOperation);
dispatch.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS,
headers);
...
See Also:
How to modify request headers in a J2EE web application
How to add HTTP header to SOAP Webservice Glassfish
Related
I'm trying to download an image from a HTTP camera. The problem is, the camera in question requires (basic) authentication. I know basic auth should not be used over HTTP, but we're talking about an isolated network, so that's not the point here.
I'm trying to use Java17 native java.net.http.HttpClient for this. I'm using the following code:
protected BufferedImage retrieveImage(URI uri) throws IOException, InterruptedException {
log.trace("Using authorization header of '{}'", basicAuthString);
HttpRequest request = HttpRequest.newBuilder()
.uri(uri)
.header("Authorization", basicAuthString)
.GET()
.timeout(Duration.ofSeconds(20))
.build();
return retrieveImage(request);
}
private BufferedImage retrieveImage(HttpRequest request) throws IOException, InterruptedException {
HttpResponse<byte[]> response = null;
try {
response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
} catch (ConnectException ex) {
log.error("Connection refused when downloading image from " + uri.toString());
return null;
}
if (response.statusCode() != 200) { // ok
log.error("Error retrieving image, status code is {}", response.statusCode());
return null;
}
ByteArrayInputStream bis = new ByteArrayInputStream(response.body());
BufferedImage image = ImageIO.read(bis);
return image;
}
The HttpClient variable (client in the above code) is created by:
this.client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NORMAL)
.authenticator(new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(camera.getLogin(), camera.getPassword().toCharArray());
}
})
.version(HttpClient.Version.HTTP_1_1)
.build();
Since I'm using Basic authorization, I can pre-calculate the auth header and store it internally. And this code is executed, as in the logs I can see the following:
2022-05-04 12:40:00.183 [TRACE] [pool-2-thread-78] Retrieving image from http://<censored>:7020/ISAPI/Streaming/channels/1/picture
2022-05-04 12:40:00.183 [TRACE] [pool-2-thread-78] Using authorization header of 'Basic <censored>'
2022-05-04 12:40:00.491 [ERROR] [pool-2-thread-78] Error retrieving image, status code is 401
However, what actually gets sent over TCP does not include the Authorization header. The following is a "Follow TCP" dump from Wireshark:
GET /ISAPI/Streaming/channels/1/picture HTTP/1.1
Content-Length: 0
Host: <censored>:7020
User-Agent: Java-http-client/17.0.3
HTTP/1.1 401 Unauthorized
Date: Wed, 04 May 2022 12:39:59 GMT
Server: webserver
X-Frame-Options: SAMEORIGIN
Content-Length: 178
Content-Type: text/html
Connection: close
WWW-Authenticate: Digest qop="auth", realm="<censored>", nonce="<censored>", stale="FALSE"
<!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>
I know the response asks for Digest authorization, and I'm using Basic. That's not the point, the camera supports basic authorization as well. The point is, the "Authorization:" header is not even sent with the request.
It's hard to find any good guides for using Java17 native HttpClient, as most of them cover the Apache one. I found one from Baeldung, but it covers the Java9. The JavaDoc is of little help, either. I am also using Authenticator, as Baeldung suggested, but it doesn't kick in, either.
What am I doing wrong?
You are doing nothing wrong. It seems the new HttpClient does not support Digest authentication out of the box. See here: https://bugs.openjdk.org/browse/JDK-8285888
The suggested way is to implement digest authentication yourself on application level.
I want to dialogue with this API service programmatically using Jersey (https://eclipse-ee4j.github.io/jersey/)
here's the Rest Controller implementation in Spring:
#PostMapping(
value = "/api/my-endpoint",
consumes = MediaType.MULTIPART_MIXED_VALUE)
public void enrichInvoice(#RequestPart("metadata") Map<String, Object> request,
#RequestPart("human") MultipartFile humanFile) {
log.info(String.format("received request:%n%s", request));
}
my client implementation would be like this
...
final Client client = ClientBuilder.newClient(new ClientConfig()
.register(MultiPartFeature.class)
.register(JacksonFeature.class)
);
final FileDataBodyPart filePart = new FileDataBodyPart("human",myFile()));
final BodyPart metadata = new BodyPart().entity(voBuilder.generateMetadata());
final MultiPart multiPartEntity = new MultiPart();
multiPartEntity.bodyPart(metadata, MediaType.APPLICATION_JSON_TYPE);
multiPartEntity.bodyPart(filePart);
final WebTarget target = client
.target("http://localhost:8080/api/my-endpoint");
final Entity<MultiPart> entity = Entity
.entity(multiPartEntity, multiPartEntity.getMediaType());
log.info(entity.toString());
final Response response = target
.request()
.post(entity);
log.info(String.format("%s", response.readEntity(String.class)));
response.close();
...
But i keep getting this error:
Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'metadata' is not present]
which is because the metadata part has to be named "metadata". and I cannot find a way to name it using BodyPart. I also tried using FormDataBodyPart to build the metadata
FormDataBodyPart metadataBodyPart = new FormDataBodyPart("metadata", metadata,
MediaType.APPLICATION_JSON_TYPE);
but with the same result.
Can you help me figure out what am I missing in the bodyPart definition?
Thanks
EDIT: here's the http request sent from my client implementation
Content-Type: multipart/mixed;boundary=Boundary_1_1972899462_1597045386454
User-Agent: Jersey/2.29 (HttpUrlConnection 11.0.8)
MIME-Version: 1.0
Host: localhost:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 765
--Boundary_1_1972899462_1597045386454
Content-Type: application/json
{"value":"key"}
--Boundary_1_1972899462_1597045386454
Content-Type: application/octet-stream
Content-Disposition: form-data; filename="file.zip"; modification-date="Wed, 05 Aug 2020 16:52:52 GMT"; size=0; name="human"
--Boundary_1_1972899462_1597045386454--
]
The solution, even if not optimal, was to treat the metadata as a text file
final Path tempFile = Files.createTempFile("prefix", "suffix");
File fileMetadata = Files.write(tempFile.toAbsolutePath(), JsonUtils.toString(metadata).getBytes());
final FileDataBodyPart metadataBodyPart = new FileDataBodyPart(
"metadata",
fileMetadata,
MediaType.APPLICATION_JSON_TYPE);
final FileDataBodyPart human = new FileDataBodyPart("human", new File(humanReadableFile.getFileKey()));
try (final MultiPart multiPartEntity = new MultiPart()) {
multiPartEntity.bodyPart(metadataBodyPart);
multiPartEntity.bodyPart(human);
final Response response = client
.target("http://localhost:8080/api/my-endpoint")
.request()
.post(Entity.entity(multiPartEntity, multiPartEntity.getMediaType()));
log.debug(String.valueOf(response.getStatus()));
log.debug(response.readEntity(String.class));
}
In this way the request body has to parts named "metadata" and "human" as requested by the controller implementation and still maintain the multipart/mixed content-type.
I'm developing an Android app that supposed to send PUT request to local server and when I try the same request using curl, I get success response, but from the Android app I get error with PUT request, here is the request for both from mobile app and curl, I listened to both requests on my PC using netcat
user#Laptop:~$ nc -l 192.168.1.104 55555
PUT /api/relay/0 HTTP/1.1
Host: 192.168.1.104:55555
User-Agent: curl/7.58.0
Accept: application/json
Content-Length: 31
Content-Type: application/x-www-form-urlencoded
apikey=2E5DE48567FB10F2&value=1
user#Laptop:~$ nc -l 192.168.1.104 55555
PUT /api/relay/0 HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 9; HRY-LX1MEB Build/HONORHRY-LX1MEB)
Host: 192.168.1.104:55555
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 31
apikey=2E5DE48567FB10F2&value=1
here is my android java request
public void jsonRequestVolley(int method, String url, String requestBody) {
RequestQueue queue = Volley.newRequestQueue(context);
JsonObjectRequest jsonRequest = new JsonObjectRequest(
method,
url,
requestBody,
successResponse(),
errorResponse()
)
{
/**
* Passing some request headers
* */
#Override
public Map<String, String> getHeaders() {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Accept", "application/json");
return headers;
}
};
queue.add(jsonRequest);
}
the working curl command is
curl -X PUT -H "Accept: application/json" http://192.168.1.105:55555/api/relay/0 --data "apikey=2E5DE48567FB10F2&value=1"```
I fixed the header because of local server requirements so, it worked now it look like
PUT /api/relay/0 HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded
User-Agent: Dalvik/2.1.0 (Linux; U; Android 9; HRY-LX1MEB Build/HONORHRY-LX1MEB)
Host: 192.168.1.104:55555
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 31
I added the following to Android code
#Override
public Map<String, String> getHeaders() {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Accept", "application/json");
return headers;
}
#Override
public String getBodyContentType() {
return "application/x-www-form-urlencoded";
}
all in request code.
Trying to make a post call on one of our servers, but getting 400 BAD_REQUEST all the time
static void postUserToken()
{
final String url = "SERVER ADDRESS";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
MultiValueMap<String, String> requestBody= new LinkedMultiValueMap<>();
requestBody.add("userName", "TESTUSER");
requestBody.add("password", "TESTPASSWORD");
requestBody.add("auth", "secEnterprise");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class );
System.out.println(response);
}
get request to the same address works, post request via Postman works fine with the same body and headers
what am I missing ?
EDIT
calls from postman
POST /api/call/ HTTP/1.1
Host: SEREVERADDRESS:6405
Content-Type: application/json
Accept: application/json
User-Agent: PostmanRuntime/7.15.0
Cache-Control: no-cache
Postman-Token: token1,token2
Host: SEREVERADDRESS:6405
accept-encoding: gzip, deflate
content-length: 92
Connection: keep-alive
cache-control: no-cache
{
"password": "PASSWORD",
"auth": "secEnterprise",
"userName": "USER"
}
in response I get an object like this {"token":"longtoken"}
You are using a MultiValueMap however the json you send from postman looks like a simple Map.
This will produce {"key1":["val1"]} instead of {"key1":"val1"}
The problem might be in the
headers.setContentType(MediaType.APPLICATION_JSON);
Try using headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); or convert your data to a proper JSON.
More on this: https://www.baeldung.com/rest-template (4.4. Submit Form Data)
As far as I understand the problem and since I do not know your rest call details, I provide below the approach you can try.
Remove the following line.
requestBody.add("auth", "secEnterprise");
Add the line
headers.setHeader("auth", "secEnterprise");
If you are using other version of Apache Http Client, you can use the following code snippet.
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("header-name" , "header-value");
I am developing a Restful Api. I am using laravel as backend (with apache) and for client I am using Android (with volley library for network communications).
In one of my call I have the following:
JsonArrayRequest jsonObjReq = new JsonArrayRequest(url,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
Log.d("Response", response.toString());
//PARSE JSON RESPONSE
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
}
}){
#Override
protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
for (Map.Entry<String, String> e: response.headers.entrySet()){
Log.d(e.getKey(), e.getValue());
}
return super.parseNetworkResponse(response);
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> map = new HashMap<String, String>();
map.put("Accept-Encoding","gzip,deflate");
return map;
}
};
As you can see I set Accept-Encoding :gzip,deflate.
When laravel receive the request, the headers exist:
array (
'accept-encoding' =>
array (
0 => 'gzip,deflate',
),
'host' =>
array (
0 => '192.168.1.104',
),
'connection' =>
array (
0 => 'Keep-Alive',
),
'user-agent' =>
array (
0 => 'Apache-HttpClient/UNAVAILABLE (java 1.5)',
),
'cookie' =>
array (
0 => 'laravel_session=,
),
'cookie2' =>
array (
0 => '$Version=1',
),
)
But when android receive the response it doesn't contains Content-Encoding : gzip header, the headers that it contains are:
Transfer-Encoding﹕ chunked
Date﹕ Sun, 21 Sep 2014 15:15:37 GMT
Keep-Alive﹕ timeout=5, max=99
Set-Cookie﹕ laravel_session=
Content-Type﹕ application/json
Connection﹕ Keep-Alive
X-Powered-By﹕ PHP/5.4.9-4ubuntu2.4
Server﹕ Apache/2.2.22 (Ubuntu)
Cache-Control﹕ no-cache
When I do the same request via curl:
curl -I -H 'Accept-Encoding: gzip' url
it return:
HTTP/1.1 200 OK
Date: Sun, 21 Sep 2014 14:46:12 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.4.9-4ubuntu2.4
Cache-Control: no-cache
Set-Cookie: laravel_session=
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Type: text/html; charset=UTF-8
So, summarizing, I set Accept-Encoding:gzip,deflate, the request is receiveb by server with that headers but when response is received by android Content-Encoding doesn't exist. It is not problem of my server because curl works good.
Any suggestion? thanks
EDIT:
I am watching data sent and received using Wireshark between android and my server. I am watching other request a part from that. This other request is made with JsonObjectRequest instead of JsonArrayRequest and with wireshark I can watch the following headers.
Android -> Server
Content-Type: application/json\r\n
Host: 192.168.1.104\r\n
Connection: Keep-Alive\r\n
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.5)\r\n
[truncated] Cookie: laravel_session=
Cookie2: $Version=1\r\n
Accept-Encoding: gzip,deflate\r\n
Server -> android
Date: Sun, 21 Sep 2014 18:59:15 GMT\r\n
Server: Apache/2.2.22 (Ubuntu)\r\n
X-Powered-By: PHP/5.4.9-4ubuntu2.4\r\n
Cache-Control: no-cache\r\n
[truncated] Set-Cookie: laravel_session=
Vary: Accept-Encoding\r\n
Content-Encoding: gzip\r\n
Connection: Keep-Alive\r\n
Transfer-Encoding: chunked\r\n
Content-Type: text/html; charset=UTF-8\r\n
In this request the response contains Content-Encoding: gzip. The only different between this request and the other is that this request uses JsonObjectRequest instead JsonArrayRequest, so cant ´JsonArrayRequest use Gzip encode?