I'm trying to write servlet that will handle POST request and stream both input and output. I mean it should read one line of input, do some work on this line, and write one line of output. And it should be able to handle arbitrary long request (so also will produce arbitrary long response) without out of memory exceptions. Here's my first attempt:
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
ServletInputStream input = request.getInputStream();
ServletOutputStream output = response.getOutputStream();
LineIterator lineIt = lineIterator(input, "UTF-8");
while (lineIt.hasNext()) {
String line = lineIt.next();
output.println(line.length());
}
output.flush();
}
Now I tested this servlet using curl and it works, but when I've written client using Apache HttpClient both the client thread and server thread hangs. The client looks like that:
HttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(...);
// request
post.setEntity(new FileEntity(new File("some-huge-file.txt")));
HttpResponse response = client.execute(post);
// response
copyInputStreamToFile(response.getEntity().getContent(), new File("results.txt"));
The issue is obvious. Client does it's job sequentially in one thread - first it sends request completely, and only then starts reading the response. But server for every line of input writes one line of output, if client is not reading output (and sequential client isn't) then server is blocked trying to write to the output stream. This in turn blocks client trying to send the input to the server.
I suppose curl works, because it somehow sends input and receives output concurrently (in separate threads?). So the first question is can Apache HttpClient be configured to behave similarly as curl?
The next question is, how to improve the servlet so ill-behaving client won't cause server threads to be hanging? My first attempt is to introduce intermediate buffer, that will gather the output until the client finishes sending input, and only then servlet will start sending output:
ServletInputStream input = request.getInputStream();
ServletOutputStream output = response.getOutputStream();
// prepare intermediate store
int threshold = 100 * 1024; // 100 kB before switching to file store
File file = File.createTempFile("intermediate", "");
DeferredFileOutputStream intermediate = new DeferredFileOutputStream(threshold, file);
// process request to intermediate store
PrintStream intermediateFront = new PrintStream(new BufferedOutputStream(intermediate));
LineIterator lineIt = lineIterator(input, "UTF-8");
while (lineIt.hasNext()) {
String line = lineIt.next();
intermediateFront.println(line.length());
}
intermediateFront.close();
// request fully processed, so now it's time to send response
intermediate.writeTo(output);
file.delete();
This works, and ill-behaving client can use my servlet safely, but on the other hand for these concurrent clients like curl this solution adds an unnecessary latency. The parallel client is reading the response in separate thread, so it will benefit when the response will be produced line by line as the request is consumed.
So I think I need a byte buffer/queue that:
can be written by one thread, and read by another thread
will initially be only in memory
will overflow to disk if necessary (similarly as DeferredFileOutputStream).
In the servlet I'll spawn new thread to read input, process it, and write output to the buffer, and the main servlet thread will read from this buffer and send it to the client.
Do you know any library like to do that? Or maybe my assumptions are wrong and I should do something completely different...
To achieve simultaneously writing and reading you can use Jetty HttpClient
http://www.eclipse.org/jetty/documentation/current/http-client-api.html
I've created pull request to your repo with this code.
HttpClient httpClient = new HttpClient();
httpClient.start();
Request request = httpClient.newRequest("http://localhost:8080/line-lengths");
final OutputStreamContentProvider contentProvider = new OutputStreamContentProvider();
InputStreamResponseListener responseListener = new InputStreamResponseListener();
request.content(contentProvider).method(HttpMethod.POST).send(responseListener); //async request
httpClient.getExecutor().execute(new Runnable() {
public void run() {
try (OutputStream outputStream = contentProvider.getOutputStream()) {
writeRequestBodyTo(outputStream); //writing to stream in another thread
} catch (IOException e) {
e.printStackTrace();
}
}
});
readResponseBodyFrom(responseListener.getInputStream()); //reading response
httpClient.stop();
Related
I am developing a test for a service.
I make a first HTTP Post, send an xml file, and receive a PDF.
Then I make a second call with this PDF, and the service sends me back a .png file corresponding to this PDF.
But I get stuck at the first step when I have to retrieve the PDF file. I use the Citrus framework, and here is how I make my call and I receive the answer
runner.http(httpActionBuilder -> httpActionBuilder
.client(vdeClient)
.send()
.post(PDF_GEN_URI)
.contentType(MediaType.APPLICATION_XML_VALUE)
.payload(xml)
);
runner.http(httpActionBuilder -> httpActionBuilder
.client(vdeClient)
.receive()
.response(HttpStatus.OK)
.contentType(MediaType.APPLICATION_PDF_VALUE)
);
And then I access the payload of the answer (= The PDF)
Object pdfPayload = context.getMessageStore().getMessage("nameOfTheMessage").getPayload();
The payload seems to be correct, but when I convert it to a byte[] and write it to a new file, it is empty and does not contain what it should.
Is this a character encoding problem or something like that? Thanks
Here is how I do the conversion
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = null;
byte[] pdfBytes;
try {
out = new ObjectOutputStream(bos);
out.writeObject(pdfPayload);
out.flush();
pdfBytes = bos.toByteArray();
} finally {
try {
bos.close();
} catch (IOException ex) {
// ignore close exception
}
}
If you managed to get PDF file content as bytes than you don't need to use ObjectOutputStream. Just write your byte array as it is into a file named .pdf and you should be OK. As for downloading and uploading files though Http requests I actually wrote my own Http client that is very simple in use. My Http client doesn't provide all the width of functionality that other well known Http clients (such as Apache Http client or OK Http client) provide but the simplicity of use is the key. Here is a working example that download and saves excutable file as file named kubectl
public static void downloadFile() {
HttpClient httpClient = new HttpClient();
httpClient.setRequestHeader("Accept", "application/octet-stream");
httpClient.setConnectionUrl("https://dl.k8s.io/release/v1.20.0//bin/linux/amd64/kubectl");
ByteBuffer buffer = null;
try {
buffer = httpClient.sendHttpRequestForBinaryResponse(HttpClient.HttpMethod.GET);
System.out.println(httpClient.getLastResponseCode() + " " + httpClient.getLastResponseMessage());
} catch (IOException ioe) {
System.out.println(httpClient.getLastResponseCode() + " " + httpClient.getLastResponseMessage());
System.out.println(TextUtils.getStacktrace(ioe, "com.mgnt.stam."));
}
try {
Files.write(Paths.get("C:\\Michael\\work\\Installations\\Kubernetes\\kubectl"), buffer.array());
} catch (IOException e) {
System.out.println(TextUtils.getStacktrace(e, "com.mgnt.stam."));
}
}
Here is Javadoc for HttpClient class. In particular note methods sendHttpRequest and sendHttpRequestForBinaryResponse using those methods you can send textual or binary info as part of request body and get back textual or binary content. This Http client is part of MgntUtils library (written and maintained by me). You can get the library as Maven artifacts or on the Github (including source code and Javadoc). BTW class TextUtils used in my example is also part of MgntUtils library
I am trying to make a GET request to a certain URL and get some data that I am going to use inside a Java servlet. This is the code I am using:
final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
String httpPath = "http://path/to/the/thirdparty/service";
HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(httpPath))
.build();
HttpResponse httpResponse = null;
try {
httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
} catch (InterruptedException e) {
response.getWriter().println(e.getMessage());
}
response.setContentType("application/json");
PrintWriter out = response.getWriter();
out.println(httpResponse.body().toString());
This works perfectly and gives the desired output when I run the code from inside a standalone class inside it's main method. However, when I run this from the servelet, I can't produce the output. The response code is 200, however the content is missing with a curl error:
* transfer closed with outstanding read data remaining
* Closing connection 0
curl: (18) transfer closed with outstanding read data remaining
To see whether the length of the data is the problem, I hardcoded the output that is expected from the third party response (and commented out the call from httpClient), that gets sent out in the response correctly when I make a curl request to the servlet. Any help is much appreciated.
I have created a server via sockets with the help of quickserver.org. The server runs solidly.
Now I had to write the client that sends a request (just a string value) to the server for an instruction and waits for its response (xml as string). This works fine when the triggered process by the request on server is not very time consuming. Unfortunately the client connection breaks as far as the server needs a long time for the process and that leads for a connection break and the client doesn't get anything back.
Here is the client code:
public String sendAndReceive(String message) throws IOException {
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(this.socket.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
printWriter.print(message);
printWriter.flush();
this.socket.shutdownOutput();
String line = null;
StringBuilder xmlResponse = new StringBuilder();
while ((line = reader.readLine()) != null)
{
xmlResponse.append(line);
}
printWriter.close();
reader.close();
this.socket.close();
return xmlResponse.toString();
}
This method sends the request and waits for the response afterwards. I am not sure about the while loop but all examples I have found on web are praising this construction. On my point of view reader.readline() can be null because the server needs more time for the response and therefore the method ends without getting the response.
How is the best practice for socket clients waiting for the response patiently? What I am doing wrong?
Kind regards,
Hilderich
You are probably getting timeout.
You can use Socket.setSoTimeout(int timeout) to change timeout (in milliseconds).
I'm trying to send strings to a server from a client but it doesn't seem to be reading from the input stream.
Client
Scanner scanner = new Scanner(System.in);
Socket connection = new Socket("localhost", 13)
OutputStream out = connection.getOutputStream();
while(true) {
String message = scanner.nextLine();
IOUtils.write(message, out, "UTF-8");
out.flush();
}
Server
ServerSocket server = new ServerSocket(localhost,13);~
Socket connection = server.accept();
InputStream in = connection.getInputStream();
StringWriter writer = new StringWriter();
while(true) {
try {
IOUtils.copy(in, writer);
System.out.println(writer.toString());
} catch(IOException io) {}
}
It reads if I close the stream from the client's outputstream but I am trying to send multiple messages from the client to the server. Could someone please help
You seem to think that each time you call flush() at client-side, the server will know it and be able to know that this is the end of a message. That's not the case. IOUtils.copy() reads everything from a stream of bytes. While the stream end hasn't been reached, copy() won't return.
You can see streams as two sides of a long tube. If you pour 10 buckets of water in the at the end of the tube, all you'll get at the other side is a continuous flow of water.
If you need multiple separate messages, then you need to design a protocol allowing to separate messages, and read until the end of a message has been reached. It could be based on separators for example. Or you could send the length of the message followed by the message itself, to let the server know how many bytes it must read to get the next message.
I am currently having an issue with a Server - Client interaction.
I am needed to read multiple println that a server sends to a client, this works but after it has read the lines it doesn't seem to go back to waiting for the user of the client to enter a new command
This is the method that opens up the streams
private void openStreams() throws IOException
{
final boolean AUTO_FLUSH = true;
is = socket.getInputStream();
os = socket.getOutputStream();
fromServer = new BufferedReader(new InputStreamReader(is));
toServer = new PrintWriter(os, AUTO_FLUSH);
}
This is the method that sends the request then reads them out
private void sendRequest() throws IOException
{
String request;
String reply;
Scanner sc = new Scanner(System.in);
request = sc.nextLine();
while(!(request.equals(CLIENT_QUITTING)))
{
toServer.println(request);
while((reply = fromServer.readLine()) != null)
{
System.out.println(reply);
}
request = sc.nextLine();
}
}
It seem to be getting stuck on the inner while loop
Can anyone point me in the direction of where I am going wrong?
The receiving code somehow needs to know when to stop reading text, and when to expect the next command.
For example, the Simple Mail Transfer Protocol (SMTP) defines that the body of the mail consists of several lines, and the line .\r\n marks the end.
Another possibility is what HTTP does for chunked encoding (with bytes, but the concept is the same). It sends the body as a sequence of chunks. Each chunk consists of a length field and the data (which is length bytes long). In your case you would probably send the number of lines that the receiver may expect, and then the lines themselves.