How to submit HTTP request with an INTENTIONAL syntax error? - java

I'm trying to write a simple test where I submit a request to http://localhost:12345/%, knowing that this is an illegal URI, because I want to assert that my HTTP Server's error-handling code behaves correctly. However, I am having a hard time forcing Java to do this.
If I try to create a Java 11 HttpRequest with URI.create("localhost:12345/%"), I get a URISyntaxException, which is correct and not helpful.
Similarly, using a ws-rs WebTarget:
ClientBuilder.newBuilder().build().target("http://localhost:12345").path("/%")
builds me a WebTarget pointing to /%25, which would normally be very helpful, but is not what I want in this particular situation.
Is there a way to test my error-handling behavior without resorting to low-level bytestream manipulation?

Another possibility is just to use plain Socket - it's easy enough if you know the protocol (especially if using the new text-block feature). This will allow you to misformat the request in any way you like. Reading the response and analysing the result is - of course - a bit more involved:
String request = """
GET %s HTTP/1.1\r
Host: localhost:%s\r
Connection: close\r
\r
""".formatted("/%", port);
try (Socket client = new Socket("localhost", port);
OutputStream os = client.getOutputStream();
InputStream in = client.getInputStream()) {
os.write(request.getBytes(StandardCharsets.US_ASCII));
os.flush();
// This is optimistic: the server should close the
// connection since we asked for it, and we're hoping
// that the response will be in ASCII for the headers
// and UTF-8 for the body - and that it won't use
// chunk encoding.
byte[] bytes = in.readAllBytes();
String response = new String(bytes, StandardCharsets.UTF_8);
System.out.println("response: " + response);
}

Noah's comment lead me down the right path; I was able to do this with the URL class:
#Test
public void testUriMalformed() throws Exception {
final URL url = new URL(TEST_URI + "/%");
final HttpURLConnection connection = (HttpURLConnection)url.openConnection();
final int code = connection.getResponseCode();
final String contentType = connection.getHeaderField("Content-Type");
final String entity = IOUtils.toString(connection.getErrorStream(), Charsets.UTF_8);
assertEquals(500, code);
assertEquals(MediaType.APPLICATION_JSON, contentType);
assertTrue(entity.contains("error_id"));
}

Related

Read InputStream from file via URL query string

Is it possible to use the java URL.openStream() method to read the file into an input stream when the URL is a query string rather than a direct link to a file? E.g. the code I have is:
URL myURL = new URL("http://www.test.com/myFile.doc");
InputStream is = myURL.openStream();
This works fine for a direct file link. But what if the URL was http://www.test.com?file=myFile.doc ? Would I still be able to obtain the file stream from the server response?
Thanks!
Generally YES, it will work.
But note that URL.openStream() method doesn't follow redirects and not so agile with specifying some additional HTTP behaviours: request type, headers, etc.
I'd recommend to use Apache HTTP Client instead:
final CloseableHttpClient httpclient = HttpClients.createDefault();
final HttpGet request = new HttpGet("http://any-url");
try (CloseableHttpResponse response = httpclient.execute(request)) {
final int status = response.getStatusLine().getStatusCode();
if (status == 200) {
final InputStream is = response.getEntity().getContent();
} else {
throw new IOException("Got " + status + " from server!");
}
}
finally {
request.reset();
}
The URL class works for any url, including:
new URL("http://www.example.com/");
new URL("file://C/windows/system32/cmd.exe");
new URL("ftp://user:password#example.com/filename;type=i");
Its up to the application to do something with the data, for example download the data, or treat it as plain text.

Design pattern for using Auth Tokens?

I'm a client side developer, moving over to server side development.
One common problem I am encountering is the need to make one API call (say to get an authentication token) and then make a follow up API call to get the data I want. Sometimes, I need to make two API calls in succession for data, without an auth token as well.
Is there a common design pattern or Java library to address this issue? Or do I need to manually create the string of calls each time I need to do so?
Edit: I'm hoping for something that looks like this
CustomClassBasedOnJson myStuff = callAPI("url", getResponse("authURL"));
This would make a call to the "url" with data received from the "authURL".
The point here is that I'm stringing multiple url calls, using the result of one call to define the next one.
When doing Server side programming, it is acceptable for HTTP calls to be called synchronously.
Therefore the proper pattern is to simply make the first call, receive the result, and then use that in the next line. There is no need to separate the calls into separate threads, or asynchronous calls, unless there is major processing happening between http calls.
For example:
JsonResponseEntry getJsonReportResponse() throws IOException {
String sReportURL = "https://someurl.com/v2/report/report?" +
"startts=" + getDateYesterday("ts") +
"&endts=" + getDateNow("ts") +
"&auth=" + getAuthCode();
URL reportURL = new URL(sReportURL);
URLConnection conn = reportURL.openConnection();
BufferedReader buf = new BufferedReader(new InputStreamReader(conn.getInputStream()));
ObjectMapper mapper = new ObjectMapper();
JsonNode reportResult = mapper.readTree(buf);
return convertJSonNodeToJsonResponseEntry(reportResult);
}
String getAuthCode() throws IOException {
String sReportURL = "https://someurl.com/auth";
URL reportURL = new URL(sReportURL);
HttpURLConnection conn = (HttpURLConnection) reportURL.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
String urlParameters = "username=myUserName&password=mypassword";
DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
BufferedReader buf = new BufferedReader(new InputStreamReader(conn.getInputStream()));
ObjectMapper mapper = new ObjectMapper();
AuthResponse response = mapper.readValue(buf, AuthResponse.class);
return response.toString();
}
The function getAuthCode() is synchronously called within the URL call that requires the response.

How to post a JSON object to an URL?

I'm trying to post a JSON to a web service so that I can get a JSON in response as return, I have searched in Google but most of the response I found is for Android but not for core Java, this question is for a Swing application I'll give the code the code I used below.
Connection class
public class Connection extends Thread {
private String url1;
private JSONObject data;
String line;
//Constuctor to initialize the variables.
public Connection(String url1, JSONObject data) {
this.url1 = url1;
this.data = data;
start();
}
public void run() {
ConnectionReaderWriter();
}
//To fetch the data from the input stream
public String getResult() {
return line;
}
public String ConnectionReaderWriter() {
URL url;
HttpURLConnection connection = null;
ObjectOutputStream out;
try {
/*URL url = new URL(Url.server_url + url1); //Creating the URL.
URLConnection conn = url.openConnection(); //Opening the connection.
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data); //Posting the data to the ouput stream.
wr.flush();
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
line=rd.readLine(); //Reading the data from the input stream.
wr.close();
rd.close();*/
url = new URL(Url.server_url + url1); //Creating the URL.
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("api_key", "123456");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
//Send request
out = new ObjectOutputStream(connection.getOutputStream());
out.writeObject(data);
out.flush();
out.close();
} catch (MalformedURLException ex) {
Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
String nonet = "No Network Connection";
line = nonet;
} catch (IOException ex) {
Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
String nonet = "No Server Connection";
line = nonet;
}
return line; //Return te stream recived from the input stream.
}
}
The commented code is the one I used before when I was passing as text encoded to the URL. The function call is given below
JSONObject json = new JSONObject();
json.put("username", username);
json.put("password", passwordenc);
Connection conn = new Connection(Url.login, json);
conn.join();
On execution I get the exception shown below
Jan 20, 2014 1:18:32 PM SupportingClass.Connection ConnectionReaderWriter
SEVERE: null
java.io.NotSerializableException: org.json.JSONObject
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1180)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)
at SupportingClass.Connection.ConnectionReaderWriter(Connection.java:74)
at SupportingClass.Connection.run(Connection.java:40)
Please tell me the problem in this code or an alternative to this method.
I'll give the answer:
replace out.writeObject(data); with out.write(data.toString().getBytes());
You were trying to write the JSONObject object, and the writeObject() method will attempt to serialize the object, but it will fail since the JSONObject class does not implement Serializable.
JSon is text, so you cannot use an ObjectOutputStream. The POST method uses the first line of the content as the arguments, so you would need a blank line before the actual content:
OutputStream stream = connection.getOutputStream();
stream.write('\r');
stream.write('\n');
out = new OutputStreamWriter(stream, "UTF-8");
out.write(data.toString());
out.flush();
out.close();
EDIT: actually we need CR LF, so println() may not work.
If you find it awkward to use HttpURLConnection natively, you might use an abstraction library. There are many powerful out there. One of them is DavidWebb. You can find a long list of alternatives at the end of that page.
With this library your code would be shorter and more readable:
JSONObject nameAndPassword = new JSONObject();
// set name and password
Webb webb = Webb.create();
JSONObject result = webb.post("your_serverUrl")
.header("api_key", "123456")
.useCaches(false)
.body(nameAndPassword)
.ensureSuccess()
.asJsonObject()
.getBody();
The code shows only the part that would run in your run() method of the Thread. It is not necessary to set the Content-Type header, because this is done automatically by detecting the type of object you set as the body. The same is true for the Accept header.
I assumed that you receive a JSON Object from your REST service, since you set the "Accept" header to "application/json". For receiving a plain String one could write String result = ... .asString().getBody().
BTW the library has been developed with Android in mind, but it can be used with Swing or server-side as well. Maybe you choose another library (e.g. Jersey Client), because you have no restriction about the size. DavidWebb weighs about 20 kilobytes while most other libraries add several hundred kilobytes to your deployment artifacts.
Another thing: you use JSONObject. For small web services, this is not a problem, but if you have to marshal many and/or big objects, you could think about using JAXB + Jackson or Gson. Less code, less errors, more fun!

Trying to speed up reads from a Socket in java

I'm trying to make a client that can send HTTP requests and receive responses from web servers. I tried using Java's HttpURLConnection class but it doesn't give me enough control over what actually gets sent to the server, so I'd like to compose my own HTTP request messages and send them over a Socket. However, reading from the Socket's InputStream is prohibitively slow for some servers, and I'd like to speed that up if possible. Here's some code that I used to test how slow the reads were for the socket as compared to the HttpURLConnection:
public static void useURLConnection() throws Exception
{
URL url = new URL("http://" + hostName + "/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStream in = conn.getInputStream();
byte[] buffer = new byte[buffersize];
long start = System.currentTimeMillis();
while(in.read(buffer) != -1) { }
System.out.println(System.currentTimeMillis() - start);
}
public static void useSocket() throws Exception
{
byte[] request = ("GET / HTTP/1.1\r\nHost: " + hostName + "\r\n\r\n").getBytes();
Socket socket = new Socket(hostName, 80);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write(request);
byte[] buffer = new byte[buffersize];
long start = System.currentTimeMillis();
while(in.read(buffer) != -1) { }
System.out.println(System.currentTimeMillis() - start);
}
Both methods run in about the same amount of time for some servers, such as www.wikipedia.org, but reading from the socket is much slower -- minutes as opposed to milliseconds -- for others, such as www.google.com. Can someone explain why this is, and perhaps give me some pointers as to what, if anything, I can do to speed up the reads from the socket? Thanks.
So, HTTP/1.1 turns on keepalive by default for client requests. In your socket example, you're sending HTTP/1.1 as your version string, so you're implicitly accepting that you can support keepalive, yet you're completely disregarding it.
Basically, you're blocking trying to read more from the server, despite the fact that the server is waiting for you to do something (either send another request or close the connection.)
You need to either send a header "Connection: close" or send HTTP/1.0 as your version string.

FileNotFoundException while getting the InputStream object from HttpURLConnection

I am trying to send a post request to a url using HttpURLConnection (for using cUrl in java).
The content of the request is xml and at the end point, the application processes the xml and stores a record to the database and then sends back a response in form of xml string. The app is hosted on apache-tomcat locally.
When I execute this code from the terminal, a row gets added to the db as expected. But an exception is thrown as follows while getting the InputStream from the connection
java.io.FileNotFoundException: http://localhost:8080/myapp/service/generate
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1401)
at org.kodeplay.helloworld.HttpCurl.main(HttpCurl.java:30)
Here is the code
public class HttpCurl {
public static void main(String [] args) {
HttpURLConnection con;
try {
con = (HttpURLConnection) new URL("http://localhost:8080/myapp/service/generate").openConnection();
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setDoInput(true);
File xmlFile = new File("test.xml");
String xml = ReadWriteTextFile.getContents(xmlFile);
con.getOutputStream().write(xml.getBytes("UTF-8"));
InputStream response = con.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(response));
for (String line ; (line = reader.readLine()) != null;) {
System.out.println(line);
}
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Its confusing because the exception is traced to the line InputStream response = con.getInputStream(); and there doesn't seem to be any file involved for a FileNotFoundException.
When I try to open a connection to an xml file directly, it doesn't throw this exception.
The service app uses spring framework and Jaxb2Marshaller to create the response xml.
The class ReadWriteTextFile is taken from here
Thanks.
Edit:
Well it saves the data in the DB and sends back a 404 response status code at the same time.
I also tried doing a curl using php and print out the CURLINFO_HTTP_CODE which turns out to be 200.
Any ideas on how do I go about debugging this ? Both service and client are on the local server.
Resolved:
I could solve the problem after referring to an answer on SO itself.
It seems HttpURLConnection always returns 404 response when connecting to a url with a non standard port.
Adding these lines solved it
con.setRequestProperty("User-Agent","Mozilla/5.0 ( compatible ) ");
con.setRequestProperty("Accept","*/*");
I don't know about your Spring/JAXB combination, but the average REST webservice won't return a response body on POST/PUT, just a response status. You'd like to determine it instead of the body.
Replace
InputStream response = con.getInputStream();
by
int status = con.getResponseCode();
All available status codes and their meaning are available in the HTTP spec, as linked before. The webservice itself should also come along with some documentation which overviews all status codes supported by the webservice and their special meaning, if any.
If the status starts with 4nn or 5nn, you'd like to use getErrorStream() instead to read the response body which may contain the error details.
InputStream error = con.getErrorStream();
FileNotFound is just an unfortunate exception used to indicate that the web server returned a 404.
To anyone with this problem in the future, the reason is because the status code was a 404 (or in my case was a 500). It appears the InpuStream function will throw an error when the status code is not 200.
In my case I control my own server and was returning a 500 status code to indicate an error occurred. Despite me also sending a body with a string message detailing the error, the inputstream threw an error regardless of the body being completely readable.
If you control your server I suppose this can be handled by sending yourself a 200 status code and then handling whatever the string error response was.
For anybody else stumbling over this, the same happened to me while trying to send a SOAP request header to a SOAP service. The issue was a wrong order in the code, I requested the input stream first before sending the XML body. In the code snipped below, the line InputStream in = conn.getInputStream(); came immediately after ByteArrayOutputStream out = new ByteArrayOutputStream(); which is the incorrect order of things.
ByteArrayOutputStream out = new ByteArrayOutputStream();
// send SOAP request as part of HTTP body
byte[] data = request.getHttpBody().getBytes("UTF-8");
conn.getOutputStream().write(data);
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.d(TAG, "http response code is " + conn.getResponseCode());
return null;
}
InputStream in = conn.getInputStream();
FileNotFound in this case was an unfortunate way to encode HTTP response code 400.
FileNotFound in this case means you got a 404 from your server - could it be that the server does not like "POST" requests?
FileNotFound in this case means you got a 404 from your server
You Have to Set the Request Content-Type Header Parameter
Set “content-type” request header to “application/json” to send the request content in JSON form.
This parameter has to be set to send the request body in JSON format.
Failing to do so, the server returns HTTP status code “400-bad request”.
con.setRequestProperty("Content-Type", "application/json; utf-8");
Full Script ->
public class SendDeviceDetails extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
String data = "";
String url = "";
HttpURLConnection con = null;
try {
// From the above URL object,
// we can invoke the openConnection method to get the HttpURLConnection object.
// We can't instantiate HttpURLConnection directly, as it's an abstract class:
con = (HttpURLConnection)new URL(url).openConnection();
//To send a POST request, we'll have to set the request method property to POST:
con.setRequestMethod("POST");
// Set the Request Content-Type Header Parameter
// Set “content-type” request header to “application/json” to send the request content in JSON form.
// This parameter has to be set to send the request body in JSON format.
//Failing to do so, the server returns HTTP status code “400-bad request”.
con.setRequestProperty("Content-Type", "application/json; utf-8");
//Set Response Format Type
//Set the “Accept” request header to “application/json” to read the response in the desired format:
con.setRequestProperty("Accept", "application/json");
//To send request content, let's enable the URLConnection object's doOutput property to true.
//Otherwise, we'll not be able to write content to the connection output stream:
con.setDoOutput(true);
//JSON String need to be constructed for the specific resource.
//We may construct complex JSON using any third-party JSON libraries such as jackson or org.json
String jsonInputString = params[0];
try(OutputStream os = con.getOutputStream()){
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
}
int code = con.getResponseCode();
System.out.println(code);
//Get the input stream to read the response content.
// Remember to use try-with-resources to close the response stream automatically.
try(BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"))){
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con != null) {
con.disconnect();
}
}
return data;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Log.e("TAG", result); // this is expecting a response code to be sent from your server upon receiving the POST data
}
and call it
new SendDeviceDetails().execute("");
you can find more details in this tutorial
https://www.baeldung.com/httpurlconnection-post
The solution:
just change localhost for the IP of your PC
if you want to know this: Windows+r > cmd > ipconfig
example: http://192.168.0.107/directory/service/program.php?action=sendSomething
just replace 192.168.0.107 for your own IP (don't try 127.0.0.1 because it's same as localhost)
Please change
con = (HttpURLConnection) new URL("http://localhost:8080/myapp/service/generate").openConnection();
To
con = (HttpURLConnection) new URL("http://YOUR_IP:8080/myapp/service/generate").openConnection();

Categories

Resources