OkHttp Reverse Http - java

How to implement reverse http as described in link using OkHttp
Currently my implementation is something like
Issue is I have to parse http responses which I don't feel safe, its just a coarse implementation, if someone can provide a better alternative
String request = "POST /reverse HTTP/1.1\r\n" +
"Upgrade: PTTH/1.0\r\n" +
"Connection: Upgrade\r\n" +
"\r\n";
Socket socket = null;
try {
socket = client.socketFactory().createSocket(ip, port);
final PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
final BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// send the reverse http request
out.write(request);
out.flush();
// read the reverse http response
String reverseResponse = readResponse(in);
if (!reverseResponse.trim().startsWith("HTTP/1.1 101 Switching Protocols")) {
throw new IOException("can't setup reverse connection");
}
while (!stopped) {
String eventData = readResponse(in);
out.write("HTTP/1.1 200 OK\r\n" +
"Content- Length: 0\r\n\" + " +
"\r\n");
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Trivia: Coincidentally, there is a different guy who drafted a similar solution and coincidentally named it reverse-http as well. This spec has a Reference Java Implementation, but not useful to you, since you are in no control of the server side.
On track:
I could find a Javascript implementation for the reverse-http that you are interested in here. Take a look for implementation hints, but, I guess you already have details.
For your problem, you would need a code fragment that can handle http requests (an http server functionality), while being embedded in a http client library.
Using Okhttp (I hope you are referring this) to achieve what you want may be a bit of work. It provides Websocket support, which is a more evolved version of reverse-http. So, you may try customizing the Websocket implementation of okhttp for your case.
A simplistic solution like yours, where you need not parse the http response can be achieved by adapting the code of popular NanoHTTPD library. Latest releases have added more dependency but an older release has just 1 java file, with no dependency and ideal for such adaptations.

Related

Unsuccessful in trying to reuse Java client socket

I have a software driver which communicates with a third-party controller; I have an API for using the latter but no visibility of its source code, and the supplier is not co-operative in trying to improve things!
The situation is as follows.
To send a request to the controller, I send an XML packet as the content of an HTTP POST to a servlet, which then sends me the response. The original code, implemented by a previous developer, works stably using java.net.Socket. However, our driver is implemented such that a new socket is created for EVERY request sent and, if the driver gets busy, the third-party controller struggles to keep up in terms of socket handling. In fact, their support guy said to me: "You really need to leave 5 seconds between each request...". This simply isn't commercially acceptable.
To improve performance, I wanted to try leaving our end of the socket open and reusing the socket pretty much indefinitely (given that connections can drop unexpectedly of course, but that's the least of my concerns and is manageable). However, whatever I seem to do, the effect is that if I use Comms.getSocket(false), a new socket is created for each request and everything works OK but bottlenecks when busy. If I use Comms.getSocket(true), the following happens:
Controller is sent first request
Controller responds to first request
Controller is sent second request (maybe 5 seconds later)
Controller never responds to second request or anything after it
postRequest() keeps getting called: for the first 12 seconds, the console outputs "Input shut down ? false" but, after that, the code no longer reaches there and doesn't get past the bw.write() and bw.flush() calls.
The controller allows both HTTP 1.0 and 1.1 but their docs say zilch about keep-alive. I've tried both and the code below shows that I've added Keep-Alive headers as well but the controller, as server, I'm guessing is ignoring them -- I don't think I have any way of knowing, do I ? When in HTTP 1.0 mode, the controller certainly returns a "Connection: close" but doesn't do that in HTTP 1.1 mode.
The likelihood is then that the server side is insisting on a "one socket per request" approach.
However, I wondered if I might be doing anything wrong (or missing something) in the following code to achieve what I want:
private String postRequest() throws IOException {
String resp = null;
String logMsg;
StringBuilder sb = new StringBuilder();
StringBuilder sbWrite = new StringBuilder();
Comms comms = getComms();
Socket socket = comms.getSocket(true);
BufferedReader br = comms.getReader();
BufferedWriter bw = comms.getWriter();
if (null != socket) {
System.out.println("Socket closed ? " + socket.isClosed());
System.out.println("Socket bound ? " + socket.isBound());
System.out.println("Socket connected ? " + socket.isConnected());
// Write the request
sbWrite
.append("POST /servlet/receiverServlet HTTP/1.1\r\n")
.append("Host: 192.168.200.100\r\n")
.append("Connection: Keep-Alive\r\n")
.append("Keep-Alive: timeout=10\r\n")
.append("Content-Type: text/xml\r\n")
.append("Content-Length: " + requestString.length() + "\r\n\r\n")
.append(requestString);
System.out.println("Writing:\n" + sbWrite.toString());
bw.write(sbWrite.toString());
bw.flush();
// Read the response
System.out.println("Input shut down ? " + socket.isInputShutdown());
String line;
boolean flag = false;
while ((line = br.readLine()) != null) {
System.out.println("Line: <" + line + ">");
if (flag) sb.append(line);
if (line.isEmpty()) flag = true;
}
resp = sb.toString();
}
else {
System.out.println("Socket not available");
}
return resp; // Another method will parse the response
}
To ease testing, I provide the socket using an extra Comms helper class and a method called getSocket(boolean reuse) where I can choose to always create a new socket or reuse the one that Comms creates for me, as follows:
public Comms(String ip, int port) {
this.ip = ip;
this.port = port;
initSocket();
}
private void initSocket() {
try {
socket = new Socket(ip, port);
socket.setKeepAlive(true);
socket.setPerformancePreferences(1, 0, 0);
socket.setReuseAddress(true);
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
br = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
System.out.println("### CREATED NEW SOCKET");
}
catch (UnknownHostException uhe) {
System.out.println("### UNKNOWN HOST FOR SOCKET");
}
catch (IOException ioe) {
System.out.println("### SOCKET I/O EXCEPTION");
}
}
public BufferedReader getReader() { return br; }
public BufferedWriter getWriter() { return bw; }
public Socket getSocket(boolean reuse) {
if (! reuse) initSocket();
return socket;
}
Can anyone help ?
If we assume that keep-alive thing is working as expected, I think the line while ((line = br.readLine()) != null) is a faulty one, as this is kind of infinity loop.
readline() returns null when there is no more data to read, e.g. a EOF, or when server/client closes the connection, that will break-down your reusing socket solution, since an open stream will never cause a null to a readLine() call, but blocking.
You need to fix the alg about reading a response (why not using implemented http client?), checking content-length, and when read the amount of required data from body, go for next loop by keeping the socket alive.
After that setting flag to true, you have to know what kind of data should be read(considering mime/content-type), besides that, the length of data, so reading data using readLine() may not be a good practice here.
Also make sure server allow for persistence connection, by checking if it respects it by responsing the same connection:keep-alive header.

Microsoft Graph: Requesting an Extension returns http 400 bad request

I added an open extension to an event in a calendar and am trying to read it back.
Here is the url:
https://graph.microsoft.com/v1.0/users/{userid}/calendars/{calendarId}=/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')
I cannot get this to work in a Java program. The following combinations do work:
It works my Java program if I remove the $expand... parameter. I can also ask for certain fields, that works too.
The request works in Postman (I just have to set the token)
The request works in Graph Explorer when I log in as the owner of the calendar
Here is the extension (inside one of the events) when I use Postman to read the event. It is the last item in the event:
"extensions#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('{userid}')/calendars('{calendarId}')/events('{eventId})/extensions",
"extensions": [
{
"#odata.type": "#microsoft.graph.openTypeExtension",
"id": "Microsoft.OutlookServices.OpenTypeExtension.c.i.m.p.server.entities.outlook.Event",
"extensionName": "c.i.m.p.server.entities.outlook.Event",
"adherentId": "12346",
"timeSlotID": "346463"
}
]
Here is the Java code (Java 8, using java.io and java.net libraries):
private static void doSomething(String _accessToken) throws IOException {
String urlString = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?$expand=Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event')";
URL url = new URL(urlString);
Proxy webProxy
= new Proxy(Proxy.Type.HTTP, new InetSocketAddress({proxy-address}, {port}));
HttpURLConnection connection = (HttpURLConnection) url.openConnection(webProxy);
// Set the appropriate header fields in the request header.
connection.setRequestProperty("Authorization", "Bearer " + _accessToken);
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
connection.setReadTimeout(5000);
connection.setRequestMethod(HttpMethod.GET);
try {
connection.connect();
int responseCode = connection.getResponseCode();
System.out.println("execute(), response code = " + responseCode);
String responseMessage = connection.getResponseMessage();
System.out.println("execute(), response Message = " + responseMessage);
String responseString = null;
try {
InputStream ins = connection.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(ins));
StringBuffer sb=new StringBuffer();
String line;
while ((line=br.readLine()) != null) {
sb.append(line);
}
responseString = sb.toString();
} catch (Exception e) {
System.out.println("Could not get input stream from response, error is " + e.toString());
}
System.out.println("execute(), httpResult = " + responseString);
} catch (IOException e) {
System.out.println(".execute(), IOException : " + e.toString());
} finally {
connection.disconnect();
}
}
How do I fix this? Thanks!
400 means bad request. It could be because of url encoding. Url encode the query string.
Something like
String query = "Extensions($filter=Id eq 'c.i.m.p.server.entities.outlook.Event'";
String url = "https://graph.microsoft.com/v1.0/users/{userId}/calendars/{calendarId}/events?
$expand=" + URLEncoder.encode(query, StandardCharsets.UTF_8.name());
Alternatively you could use graph service java api based on your need which will help abstract all the interactions for you or you could use any of the rest clients available.
First of all, you should provide more info on the error - Stacktrace and error message. But 400 code indicates that was a user mistake, meaning that you are sending an invalid request. Since you say that postman request works then compare all the headers that are sent by postman and see if your code misses some hearer. As for the code, instead of coding your own Http client functionality I would suggest using 3d party Http client. Here are a few suggestions:
Apache Http client - very popular and well known 3d party Http Client
OK Http client - Open-source Http client. Here is tutorial
MgntUtils Http client - very simple 3d party HttpClient: Provided in MgntUtils Open source library (written by me). Very simple in use. Take a look at Javadoc. Library itself provided as Maven artifacts and on Git (including source code and Javadoc).

CXF service disable chunking in server response

I am replacing a .NET web service with a Java CXF web service using SOAP and JAXWS. The Client is fixed, and believe it or not, black-boxed. I am trying to get the service to work identically using the Java Service and the original .NET client. I have tried many approaches and confirmed that the response to the client is identical (bytes) to the .NET service. I have tried returning the Windows response from the Java service to no avail which means I have an issue related to the transmission, possibly encoding. I noticed that the response transfer-encoding=[chunked] - which I suspect may be an issue. I have not found a way to change this on the server...but since I cant modify the client, I need a work around. I am not familiar with the .NET API but here is the message I get:
BTW - the Error from the client is [6] ERROR - [sweeper].[SweeperService.CollectSettingsInformation] <11501> (9990) There is an error in XML document (1, 2). at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
at System.Xml.Serialization.XmlSerializer.Deserialize(TextReader textReader)
at client.ReadMessageEnvelope(String xml)
at client.Translate(String xml)
at client.CollectSettingsInformation()
ANY Ideas would be appreciated.
Adapted from several other similar solutions:
public void handleMessage(Message message) {
boolean isOutbound = false;
isOutbound = message == message.getExchange().getOutMessage() || message == message.getExchange().getOutFaultMessage();
if (isOutbound) {
OutputStream os = message.getContent(OutputStream.class);
CachedOutputStream cs = new CachedOutputStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
IOUtils.closeQuietly(cs);
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
csnew.flush();
IOUtils.closeQuietly(csnew);
int bytes = Math.toIntExact(currentEnvelopeMessage.getBytes().length);
HttpServletResponse response = (HttpServletResponse) message.get(AbstractHTTPDestination.HTTP_RESPONSE);
if(null != response){
log.debug("Setting Content Length: " + bytes);
response.setContentLength(bytes);
}
InputStream replaceInStream = IOUtils.toInputStream(currentEnvelopeMessage, "UTF-8");
os.flush();
if (os instanceof CopyingOutputStream)
((CopyingOutputStream)os).copyFrom( replaceInStream );
else
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
message.setContent(OutputStream.class, os);
IOUtils.closeQuietly(os);
} catch (IOException ioe) {
log.warn("Unable to perform change.", ioe);
throw new RuntimeException(ioe);
}
}
}

Apache Jackrabbit webDAV PUT Method.

beginning to work with Jackrabbit 2.6.3 and commons httpclient 3.
I've written a simple webDav client to upload a file to a server and want to test it against jackrabbit's standalone server's default repository. Simply I just want to put a file in the default repository.
my httpclient connects and the method begins buffering my file. Trouble is, I can't seem to work what URL I should point my http method at to correctly put it in the repository. The standalone server is running on:
http://localhost:8080.
I seem to either get a 405 PUT not supported or 404 or 403 or the even more curious "repository '/' does not begin with '/default' for all the urls I've tried. I can see the default repository content if I point my browser at:
http://localhost:8080/repository/default/
Simply, my question is, what is the url to do this with a PutMethod? As rudimentary as that sounds.
I've included some truncated code for the class I've written, specifically the method I'm working with at the moment, I think it should be enough to show my approach is correct.
public void insertFile(byte[] content, String id) throws Exception {
PutMethod httpMethod = new PutMethod("http://localhost:8080/repository/default/");
InputStream is = new ByteArrayInputStream(content);
FileMetaData meta = new FileMetaData();
meta.address = destUri;
meta.id = id;
meta.mimeType = Files.probeContentType(Paths.get(meta.address));
RequestEntity requestEntity = new InputStreamRequestEntity(is, meta.mimeType);
httpMethod.setRequestEntity(requestEntity);
try {
int statusCode = client.executeMethod(httpMethod);
if (statusCode != HttpStatus.SC_OK) System.err.println("Method failed: " + httpMethod.getStatusLine());
byte[] responseBody = httpMethod.getResponseBody();
System.out.println(new String(responseBody));
} catch (HttpException e) {
System.err.println("Fatal protocol violation: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.err.println("Fatal transport error: " + e.getMessage());
e.printStackTrace();
} finally {
httpMethod.releaseConnection();
}
}
I'm sure it's a simple answer, but trawling the docs don't seem to show up any resources or tutorials relating to this. Any help appreciated.
Really silly, totally thought that the requestEntity would send some data on file and use that as a file name, instead I have to specify that myself. PROTIP: Step away from the computer for 5 - 10 minutes. For anyone else with my issue it should be '"yourUrl/repository/default" + fileName'. Easy.

send http request to linux server

I have to send an HTTP request to our C programme which is running on a Linux machine. How can I send an HTTP request in Java to our server which is in C and running on a Linux machine?
public void sendPostRequest() {
//Build parameter string
String data = "width=50&height=100";
try {
// Send the request
URL url = new URL("http://www.somesite.com");
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
//write parameters
writer.write(data);
writer.flush();
// Get the response
StringBuffer answer = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
answer.append(line);
}
writer.close();
reader.close();
//Output the response
System.out.println(answer.toString());
} catch (MalformedURLException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
The above example is for sending a POST request using a URL.
If you're asking how to send an HTTP request in Java to a web server written in C, you can use the URLConnection class.
try {
// Construct data
String data = URLEncoder.encode("key1", "UTF-8") + "=" + URLEncoder.encode("value1", "UTF-8");
data += "&" + URLEncoder.encode("key2", "UTF-8") + "=" + URLEncoder.encode("value2", "UTF-8");
// Send data
URL url = new URL("http://hostname:80/cgi");
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
// Process line...
}
wr.close();
rd.close(); } catch (Exception e) { }
The above example is for sending a POST request using a URL.
Also take a look at Sun Tutorial on reading/Writing from/to a URLConnection. The other option is to use Apache HTTPComponents which has examples for the HttpCore and HttpClient module.
If you are looking into implementing the web Server, you will have to handle the Http request yourselves which involves a thread pool, parsing the requests, generating HTML, security, multiple sessions, etc or follow the easy route by using off-the-shelf web server like Apache and seeing which all high-level languages like Perl, Ruby can be used for developing the web application.
For implementing your own Http server, please take a look at Micro-Httpd or tinyHttpd
You may also want to look at Adding Web Interface -C++ application which has a sample code.
From the way your question is worded.. I think you need to know some basic stuff before you can start. Try try googling for a simple guide to how web servers work.
Once you have the basic idea, there are a couple of options for a C programmer:
1) You want your C program to be running continuously, waiting for a request from your Java.
In this case, you will have to code your C program to open a Socket and Listen for connections. See http://www.linuxhowtos.org/C_C++/socket.htm for example.
OR
2) You have a web Server on your server which will run your C program each time a particular request is made? In this case, you will have to code your C as a CGI program. See http://www.cs.tut.fi/~jkorpela/forms/cgic.html for example.
Hint: (2) is much easier!

Categories