Special characters in SOAP payload not transferred correctly - java

I have written a SOAP web-service using CXF which is being called by a SAP system, in the payload there is a word with a special character which occurs multiple times. However, I read this word differently in some random cases, i.e. in a one payload I see the word as Kliëntbestuurder and in another as Kli��ntbestuurder.
The SAP system calling my service via SAP PI only have the one word.
UPDATE:
So it seems that it was not the web-service communication that was getting confused but rather the interceptor that I had written to dump the soap envelope for me to be able to scrutinise. The interceptor is as follows:
public class WebServiceMessageInterceptor extends AbstractPhaseInterceptor<Message> {
public WebServiceMessageInterceptor() {
super(Phase.RECEIVE);
}
#Override
public void handleMessage(Message message) throws Fault {
final LoggingMessage buffer = new LoggingMessage("", "");
String encoding = (String) message.get(Message.ENCODING);
if (encoding != null) {
buffer.getEncoding().append(encoding);
}
Object headers = message.get(Message.PROTOCOL_HEADERS);
if (headers != null) {
buffer.getHeader().append(headers);
}
InputStream is = message.getContent(InputStream.class);
if (is != null) {
CachedOutputStream outputStream = new CachedOutputStream();
try {
IOUtils.copy(is, outputStream);
outputStream.flush();
is.close();
message.setContent(InputStream.class, outputStream.getInputStream());
outputStream.writeCacheTo(buffer.getPayload(), "UTF-8", -1);
outputStream.close();
FileUtils.writeStringToFile(new File("/tmp/soap" + System.currentTimeMillis() + ".log"), buffer.toString(), "UTF-8");
} catch (IOException e) {
e.printStackTrace();
throw new Fault(e);
}
}
}
Any further ideas why my interceptor is not using UTF-8?

This might be related to not using encoding consistently across and within the services. I suggest you help yourself by reading this excellent tutorial - Unicode - How to get the characters right? end to end. Then ask follow up questions once you narrowed down the scope of the error.

Check the http headers on the response you are sending back from your web services. You can use the Raw tab in soapUI to view the headers. If you don't see something like
Content-Type: text/xml;charset=UTF-8
then you can force CXF to add it to the response by doing something like this in your WebMethods:
MessageContext ctx = context.getMessageContext();
ctx.put(Message.CONTENT_TYPE, "text/xml;charset=UTF-8");
where context is the javax.xml.ws.WebServiceContext injected into your class.
You should also verify that the client to your web service is also using the correct encoding. You may be sending a valid response to him.

Related

302 Response for session management from ajax request

I am trying to manage a user session by making an ajax request to java code repeatedly
function sendSessionKeepAliveRequest() {
$.get('${URL}/sessionKeepAlive?nd=' + new Date().getTime());
}
and java code (spring framework used) handling this request:
#RequestMapping("/sessionKeepAlive")
public String dummySessionKeepAlive(HttpServletResponse response,
HttpServletRequest request) {
PrintWriter writer = null;
try {
writer = response.getWriter();
} catch (IOException e) {
logger.error(e.getMessage());
}
if (writer != null) {
response.setContentType("application/json");
// Sending an empty JSON response.
Gson gson = new Gson();
writer.write(gson.toJson(""));
}
return null;
}
Now the issue is some times were are getting 302 Found instead of 200 OK which makes jsessionid change and session got time out.I have tested in on IE and FF and both of the browser have same behaviour.
Code is deployed on IBM websphere v7.0
Please help or any direction.Please feel free if any more inputs are required or I need to modify my question.
Kind Regards
You have encountered a so-called redirection: The url of the resource you've requested has changed. The new url is provided in the http header 'Location'.
You can either read out this location and issue another Request using this url or you can set up your response handling code to automatically follow the redirection.
Sample code:
function sendSessionKeepAliveRequest() {
$.ajax(
url: '${URL}/sessionKeepAlive?nd=' + new Date().getTime()
, statusCode: {
302: function ( jqXHR, textStatus, errorThrown ) {
var url_trg = jqXHR.getResponseHeader('Location');
$.get(url_trg);
}
}
});
Update
jquery ajax requests should handle 302 status codes automatically, so there might be some other problem. Could it possibly be a cross-domain issue ?
If the purpose is just only to alive session then no need to use GSON you can pass empty String and add one annotation
#ResposeBody
This will help you to get ajax response.

File upload along with other object in Jersey restful web service

I want to create an employee information in the system by uploading an image along with employee data. I am able to do it with different rest calls using jersey. But I want to achieve in one rest call.
I provide below the structure. Please help me how to do in this regard.
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
Employee emp) {
//..... business login
}
Whenever I am trying to do, I get error in Chrome postman. The simple structure of my Employee json is given below.
{
"Name": "John",
"Age": 23,
"Email": "john#gmail.com",
"Adrs": {
"DoorNo": "12-A",
"Street": "Street-11",
"City": "Bangalore",
"Country": "Karnataka"
}
}
However I can do it by making two different call, but I want to achieve in one rest call so that I can receive the file as well as the actual data of the employee.
Request you to help in this regard.
You can't have two Content-Types (well technically that's what we're doing below, but they are separated with each part of the multipart, but the main type is multipart). That's basically what you are expecting with your method. You are expecting mutlipart and json together as the main media type. The Employee data needs to be part of the multipart. So you can add a #FormDataParam("emp") for the Employee.
#FormDataParam("emp") Employee emp) { ...
Here's the class I used for testing
#Path("/multipart")
public class MultipartResource {
#POST
#Path("/upload2")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadFileWithData(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("emp") Employee emp) throws Exception{
Image img = ImageIO.read(fileInputStream);
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
System.out.println(cdh.getName());
System.out.println(emp);
return Response.ok("Cool Tools!").build();
}
}
First I just tested with the client API to make sure it works
#Test
public void testGetIt() throws Exception {
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
WebTarget t = client.target(Main.BASE_URI).path("multipart").path("upload2");
FileDataBodyPart filePart = new FileDataBodyPart("file",
new File("stackoverflow.png"));
// UPDATE: just tested again, and the below code is not needed.
// It's redundant. Using the FileDataBodyPart already sets the
// Content-Disposition information
filePart.setContentDisposition(
FormDataContentDisposition.name("file")
.fileName("stackoverflow.png").build());
String empPartJson
= "{"
+ " \"id\": 1234,"
+ " \"name\": \"Peeskillet\""
+ "}";
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
Response response = t.request().post(
Entity.entity(multipartEntity, multipartEntity.getMediaType()));
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
response.close();
}
I just created a simple Employee class with an id and name field for testing. This works perfectly fine. It shows the image, prints the content disposition, and prints the Employee object.
I'm not too familiar with Postman, so I saved that testing for last :-)
It appears to work fine also, as you can see the response "Cool Tools". But if we look at the printed Employee data, we'll see that it's null. Which is weird because with the client API it worked fine.
If we look at the Preview window, we'll see the problem
There's no Content-Type header for the emp body part. You can see in the client API I explicitly set it
MultiPart multipartEntity = new FormDataMultiPart()
.field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
.bodyPart(filePart);
So I guess this is really only part of a full answer. Like I said, I am not familiar with Postman So I don't know how to set Content-Types for individual body parts. The image/png for the image was automatically set for me for the image part (I guess it was just determined by the file extension). If you can figure this out, then the problem should be solved. Please, if you find out how to do this, post it as an answer.
See UPDATE below for solution
And just for completeness...
See here for more about MultiPart with Jersey.
Basic configurations:
Dependency:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey2.version}</version>
</dependency>
Client config:
final Client client = ClientBuilder.newBuilder()
.register(MultiPartFeature.class)
.build();
Server config:
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.multipart")
.register(MultiPartFeature.class);
If you're having problems with the server configuration, one of the following posts might help
What exactly is the ResourceConfig class in Jersey 2?
152 MULTIPART_FORM_DATA: No injection source found for a parameter of type public javax.ws.rs.core.Response
UPDATE
So as you can see from the Postman client, some clients are unable to set individual parts' Content-Type, this includes the browser, in regards to it's default capabilities when using FormData (js).
We can't expect the client to find away around this, so what we can do, is when receiving the data, explicitly set the Content-Type before deserializing. For example
#POST
#Path("upload2")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(#FormDataParam("emp") FormDataBodyPart jsonPart,
#FormDataParam("file") FormDataBodyPart bodyPart) {
jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
Employee emp = jsonPart.getValueAs(Employee.class);
}
It's a little extra work to get the POJO, but it is a better solution than forcing the client to try and find it's own solution.
Another option is to use a String parameter and use whatever JSON library you use to deserialze the String to the POJO (like Jackson ObjectMapper). With the previous option, we just let Jersey handle the deserialization, and it will use the same JSON library it uses for all the other JSON endpoints (which might be preferred).
Asides
There is a conversation in these comments that you may be interested in if you are using a different Connector than the default HttpUrlConnection.
You can access the Image File and data from a form using MULTIPART FORM DATA By using the below code.
#POST
#Path("/UpdateProfile")
#Consumes(value={MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
#Produces(value={MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response updateProfile(
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
#FormDataParam("ProfileInfo") String ProfileInfo,
#FormDataParam("registrationId") String registrationId) {
String filePath= "/filepath/"+contentDispositionHeader.getFileName();
OutputStream outputStream = null;
try {
int read = 0;
byte[] bytes = new byte[1024];
outputStream = new FileOutputStream(new File(filePath));
while ((read = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
outputStream.flush();
outputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch(Exception ex) {}
}
}
}
When I tried #PaulSamsotha's solution with Jersey client 2.21.1, there was 400 error. It worked when I added following in my client code:
MediaType contentType = MediaType.MULTIPART_FORM_DATA_TYPE;
contentType = Boundary.addBoundary(contentType);
Response response = t.request()
.post(Entity.entity(multipartEntity, contentType));
instead of hardcoded MediaType.MULTIPART_FORM_DATA in POST request call.
The reason this is needed is because when you use a different Connector (like Apache) for the Jersey Client, it is unable to alter outbound headers, which is required to add a boundary to the Content-Type. This limitation is explained in the Jersey Client docs. So if you want to use a different Connector, then you need to manually create the boundary.
Your ApplicationConfig should register the MultiPartFeature.class from the glassfish.jersey.media.. so as to enable file upload
#javax.ws.rs.ApplicationPath(ResourcePath.API_ROOT)
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
//register the necessary headers files needed from client
register(CORSConfigurationFilter.class);
//The jackson feature and provider is used for object serialization
//between client and server objects in to a json
register(JacksonFeature.class);
register(JacksonProvider.class);
//Glassfish multipart file uploader feature
register(MultiPartFeature.class);
//inject and registered all resources class using the package
//not to be tempered with
packages("com.flexisaf.safhrms.client.resources");
register(RESTRequestFilter.class);
}
I used file upload example from,
http://www.mkyong.com/webservices/jax-rs/file-upload-example-in-jersey/
in my resource class i have below method
#POST
#Path("/upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response attachupload(#FormDataParam("file") byte[] is,
#FormDataParam("file") FormDataContentDisposition fileDetail,
#FormDataParam("fileName") String flename){
attachService.saveAttachment(flename,is);
}
in my attachService.java i have below method
public void saveAttachment(String flename, byte[] is) {
// TODO Auto-generated method stub
attachmentDao.saveAttachment(flename,is);
}
in Dao i have
attach.setData(is);
attach.setFileName(flename);
in my HBM mapping is like
<property name="data" type="binary" >
<column name="data" />
</property>
This working for all type of files like .PDF,.TXT, .PNG etc.,
The request type is multipart/form-data and what you are sending is essentially form fields that go out as bytes with content boundaries separating different form fields.To send an object representation as form field (string), you can send a serialized form from the client that you can then deserialize on the server.
After all no programming environment object is actually ever traveling on the wire. The programming environment on both side are just doing automatic serialization and deserialization that you can also do. That is the cleanest and programming environment quirks free way to do it.
As an example, here is a javascript client posting to a Jersey example service,
submitFile(){
let data = new FormData();
let account = {
"name": "test account",
"location": "Bangalore"
}
data.append('file', this.file);
data.append("accountKey", "44c85e59-afed-4fb2-884d-b3d85b051c44");
data.append("device", "test001");
data.append("account", JSON.stringify(account));
let url = "http://localhost:9090/sensordb/test/file/multipart/upload";
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
axios.post(url, data, config).then(function(data){
console.log('SUCCESS!!');
console.log(data.data);
}).catch(function(){
console.log('FAILURE!!');
});
},
Here the client is sending a file, 2 form fields (strings) and an account object that has been stringified for transport. here is how the form fields look on the wire,
On the server, you can just deserialize the form fields the way you see fit. To finish this trivial example,
#POST
#Path("/file/multipart/upload")
#Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadMultiPart(#Context ContainerRequestContext requestContext,
#FormDataParam("file") InputStream fileInputStream,
#FormDataParam("file") FormDataContentDisposition cdh,
#FormDataParam("accountKey") String accountKey,
#FormDataParam("account") String json) {
System.out.println(cdh.getFileName());
System.out.println(cdh.getName());
System.out.println(accountKey);
try {
Account account = Account.deserialize(json);
System.out.println(account.getLocation());
System.out.println(account.getName());
} catch (Exception e) {
e.printStackTrace();
}
return Response.ok().build();
}

Write message content and response code in Apache CXF Interceptor

I am trying to make my web service secure by making one of the methods require HTTP Basic authentication. In order to do this, I've implemented a custom Interceptor called LoginInterceptor that checks the requested URL, and if it corresponds to a method called open, it checks the message header for the username and password.
If there are no authentication fields in the header, the response code is set to HTTP_UNAUTHORIZED, and if the username or password is incorrect, the response code is set to HTTP_FORBIDDEN. Here's the code:
public LoginInterceptor() {
super(Phase.RECEIVE);
addAfter(RequestInterceptor.class.getName()); //another custom interceptor, for some simple redirections.
}
public void handleMessage(Message message) throws Fault {
String requestURI = message.get(Message.REQUEST_URI).toString();
String methodKeyword = requestURI.substring("localhost".length()+1).split("/")[0];
if(methodKeyword.equals("open")) {
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
if(policy == null) {
sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
return;
}
//userPasswords is a hashmap of usernames and passwords.
String realPassword = userPasswords.get(policy.getUserName());
if (realPassword == null || !realPassword.equals(policy.getPassword())) {
sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
}
}
}
//This is where the response code is set, and this is where I'd like to write the response message.
private void sendErrorResponse(Message message, int responseCode) {
Message outMessage = getOutMessage(message);
outMessage.put(Message.RESPONSE_CODE, responseCode);
// Set the response headers
Map responseHeaders = (Map) message.get(Message.PROTOCOL_HEADERS);
if (responseHeaders != null) {
responseHeaders.put("Content-Type", Arrays.asList("text/html"));
responseHeaders.put("Content-Length", Arrays.asList(String.valueOf("0")));
}
message.getInterceptorChain().abort();
try {
getConduit(message).prepare(outMessage);
close(outMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
private Message getOutMessage(Message inMessage) {
Exchange exchange = inMessage.getExchange();
Message outMessage = exchange.getOutMessage();
if (outMessage == null) {
Endpoint endpoint = exchange.get(Endpoint.class);
outMessage = endpoint.getBinding().createMessage();
exchange.setOutMessage(outMessage);
}
outMessage.putAll(inMessage);
return outMessage;
}
//Not actually sure what this does. Copied from a tutorial online. Any explanation is welcome
private Conduit getConduit(Message inMessage) throws IOException {
Exchange exchange = inMessage.getExchange();
Conduit conduit = exchange.getDestination().getBackChannel(inMessage);
exchange.setConduit(conduit);
return conduit;
}
private void close(Message outMessage) throws IOException {
OutputStream os = outMessage.getContent(OutputStream.class);
os.flush();
os.close();
}
This works fine, however, I want to also return a message in the response, something like "incorrect username or password". I've tried, from within the sendErrorResponse method, doing:
outMessage.setContent(String.class, "incorrect username or password")
and I set the content-length to "incorrect username or password".length(). This doesn't work, I guess because the Apache CXF Messages use InputStreams and OutputStreams.
So I tried:
OutputStream os = outMessage.getContent(OutputStream.class);
try {
os.write("incorrect username or password".getBytes() );
outMessage.setContent(OutputStream.class, os);
} catch (IOException e) {
e.printStackTrace();
}
This doesn't work either. When stepping through with a debugger, I notice that os is null When testing with Postman, I get:
Could not get any response This seems to be like an error connecting
to http://localhost:9090/launcher/open. The response status was 0.
Check out the W3C XMLHttpRequest Level 2 spec for more details about
when this happens.
Pressing ctrl+shif+c (opening up dev tools) in Chrome, and checking the networks tab, I see:
"ERR_CONTENT_LENGTH_MISMATCH"
I've tried using an XMLStreamWriter, but that wans't any better.
Questions:
I can return the correct response code (401 Unauthorized and 403 forbidden), but how do I return a message in the response body?
Do I need to specifically extend a particular OutInterceptor like JASRXOutInterceptor in order to modify the message content?
I tried using a JAASInterceptor before, but I didn't manage to get that working. Could someone show me how to implement it that way, if that's somehow easier?
I could also just throw a fault like this: throw new Fault("incorrect username or password", Logger.getGlobal());, but then the HTTP response code would be 500. I'd prefer to return a proper 401 or 403 response.
Note:
Right now I'm still using HTTP for the transport layer. Once I fix this, I'll change to HTTPS.
Basically, what I wanted to do is return a fault with a HTTP response code of 401 (unauthorized) or 403 (forbidden) instead of 500 (server error). Turns out Apache CXF provides a simple way of doing that, using the Fault.setStatusCode(int) method, as I found from this question on Stack Overflow: how to throw a 403 error in Apache CXF - Java
So this is what my handleMessage method looks like now:
public void handleMessage(Message message) throws Fault {
String requestURI = message.get(Message.REQUEST_URI).toString();
String methodKeyword = requestURI.substring("localhost".length()+1).split("/")[0];
if(methodKeyword.equals("open")) {
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
if(policy == null) {
Fault fault = new Fault("incorrect username or password", Logger.getGlobal());
fault.setStatusCode(401);
throw fault;
}
String realPassword = userPasswords.get(policy.getUserName());
if (realPassword == null || !realPassword.equals(policy.getPassword())) {
Fault fault = new Fault("incorrect username or password", Logger.getGlobal());
fault.setStatusCode(403);
throw fault;
}
}
}
I removed the other methods, they were unnecessary.

Java- redirecting a request

I'm writing a Servlet in Java, that basically, gets a request with a XML in the Requests body, and then changes a few things in the XML and redirect/foreword the request with The new XML to a different Servlet that's on the same server, but its on a different web app.
How do redirect/foreword the request with The new XML? can i find code example any where?
this is what i have so far:
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String body = getBody(request);
MapXml mapXml = new MapXml(body,
"C:\\Projects\\XmlMapper\\output.xml","C:\\Projects\\XmlMapper\\output\\");
String outputXml = mapXml.getOutputXml();
}
public static String getBody(HttpServletRequest request) throws IOException {
String body = null;
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
return body;
}
And i have no idea how to continue on from here. I'm new to the servlet world.. Thanks!!! Cheers:)
If both web-apps are on the same server, i.e. Tomcat
in its META-INF/context.xml set <Context crossContext="true" />
getServletContext().getContext("/app").getRequestDispatcher("f.jsp").forward(..);,
where app is the name of the other application.
Or what you maybe should do is, Use URLConnection to send request to any URL.
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
About how to set XML in request, you can carry relatively larger amounts of data in POST request. You can also find the max limit of POST data.
just read the bytes that make up the XML file (using FileInputStream)
(if you dont have xml in file, use String to create ur xml)and send
them in the POST body. Make sure to set the content encoding
accordingly.
I am editing this for the 4th time, to add more details.
You can use Apache HTTP Client to post XML easily if its difficult for you to use Java's HTTP client.
String xml = "your xml";
PostMethod post = new PostMethod(strURL);
try {
StringRequestEntity requestEntity = new StringRequestEntity(xml);
post.setRequestEntity(requestEntity); ..
....
...
Because the xml will not be a small body for request,so you have to let the client post the new xml for you. or you can do:
share the same database or cache with remote web service, and forward the key of the data in database or cache.
use HttpClient to post the request for your client, with modified xml, and return the response from remote service to your client.
if you can make sure the xml body is small, you can just using GET method, forward the request to remote server
Let's rule out some possibilities first:
You cannot do response.sendRedirect("/otherapp/servlet.do") since it doesn't let you send POST data to another webapp.
You cannot use session since you're sending data across to a different webapp.
You cannot obviously pass full XML in a query string using GET.
Once those possibilities are ruled out only possible way I can think of is this:
Return to the calling page with modified XML and URL of other webapps's servelt in response
Let calling page immediately POST the modified XML to other webapps's servelt using simple Javascript

Java Servlet 3.0 server push: Sending data multiple times using same AsyncContext

Lead by several examples and questions answered here ( mainly
http://www.javaworld.com/javaworld/jw-02-2009/jw-02-servlet3.html?page=3 ), I want to have server sending the response multiple times to a client without completing the request. When request times out, I create another one and so on.
I want to avoid long polling, since I have to recreate request every time I get the response. (and that quite isn't what async capabilities of servlet 3.0 are aiming at).
I have this on server side:
#WebServlet(urlPatterns = {"/home"}, name = "async", asyncSupported = true)
public class CometServlet extends HttpServlet {
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
AsyncContext ac = request.startAsync(request, response);
HashMap<String, AsyncContext> store = AppContext.getInstance().getStore();
store.put(request.getParameter("id"), ac);
}
}
And a thread to write to async context.
class MyThread extends Thread {
String id, message;
public MyThread(String id, String message) {
this.id = id;
this.message = message;
}
public void run() {
HashMap<String, AsyncContext> store = AppContext.getInstance().getStore();
AsyncContext ac = store.get(id);
try {
ac.getResponse().getWriter().print(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
But when I make the request, data is sent only if I call ac.complete(). Without it request will always timeout. So basically I want to have data "streamed" before request is completed.
Just to make a note, I have tried this with Jetty 8 Continuation API, I also tried with printing to OutputStream instead of PrintWriter. I also tried flushBuffer() on response. Same thing.
What am I doing wrong?
Client side is done like this:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:8080/home', true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 3 || xhr.readyState == 4) {
document.getElementById("dynamicContent").innerHTML = xhr.responseText;
}
}
xhr.send(null);
Can someone at least confirm that server side is okay? :)
Your server-side and client-side code is indeed ok.
The problem is actually with your browser buffering text/plain responses from your web-server.
This is the reason you dont see this issue when you use curl.
I took your client-side code and I was able to see incremental responses, with only just one little change:
response.setContentType("text/html");
The incremental responses showed up immediately regardless of their size.
Without that setting, when my output was a small message, it was considered as text/plain and wasnt showing up at the client immediately. When I kept adding more and more to the client responses, it got accumulated until the buffer size reached about 1024 bytes and then the whole thing showed up on the client side. After that point, however, the small increments showed up immediately (no more accumulation).
I know this is a bit old, but you can just flushBuffer on the response as well.

Categories