Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.httprpc.WebServiceProxy Maven / Gradle / Ivy
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.httprpc;
import org.httprpc.beans.BeanAdapter;
import org.httprpc.io.JSONDecoder;
import org.httprpc.io.JSONEncoder;
import org.httprpc.io.TextDecoder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
/**
* Web service proxy class.
*/
public class WebServiceProxy {
/**
* Encoding options for POST requests.
*/
public enum Encoding {
/**
* The "application/x-www-form-urlencoded" encoding.
*/
APPLICATION_X_WWW_FORM_URLENCODED,
/**
* The "multipart/form-data" encoding.
*/
MULTIPART_FORM_DATA
}
/**
* Success status values.
*/
public enum Status {
/**
* Indicates that the request was successful.
*/
OK(200),
/**
* Indicates that the request resulted in the creation of a new resource.
*/
CREATED(201),
/**
* Indicates that the request returned no content.
*/
NO_CONTENT(204);
private final int code;
Status(int code) {
this.code = code;
}
}
/**
* Interface representing a request handler.
*/
public interface RequestHandler {
/**
* Returns the content type produced by the handler.
*
* @return
* The content type produced by the handler.
*/
String getContentType();
/**
* Encodes a request to an output stream.
*
* @param outputStream
* The output stream to write to.
*
* @throws IOException
* If an exception occurs.
*/
void encodeRequest(OutputStream outputStream) throws IOException;
}
/**
* Interface representing a response handler.
*/
public interface ResponseHandler {
/**
* Decodes a response from an input stream.
*
* @param inputStream
* The input stream to read from.
*
* @param contentType
* The content type, or null
if the content type is not known.
*
* @throws IOException
* If an exception occurs.
*
* @return
* The decoded value.
*/
T decodeResponse(InputStream inputStream, String contentType) throws IOException;
}
/**
* Interface representing an error handler.
*/
public interface ErrorHandler {
/**
* Handles an error response.
*
* @param errorStream
* The error stream.
*
* @param contentType
* The content type, or null
if the content type is not known.
*
* @param statusCode
* The status code.
*
* @throws IOException
* Representing the error that occurred, or if an exception occurs while
* handling the error.
*/
void handleResponse(InputStream errorStream, String contentType, int statusCode) throws IOException;
}
private class MonitoredInputStream extends InputStream {
InputStream inputStream;
MonitoredInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public int read() throws IOException {
int b = inputStream.read();
if (monitorStream != null && b != -1) {
monitorStream.write(b);
}
return b;
}
@Override
public void close() throws IOException {
inputStream.close();
}
}
private class MonitoredOutputStream extends OutputStream {
OutputStream outputStream;
MonitoredOutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public void write(int b) throws IOException {
outputStream.write(b);
if (monitorStream != null) {
monitorStream.write(b);
}
}
@Override
public void flush() throws IOException {
outputStream.flush();
}
@Override
public void close() throws IOException {
outputStream.close();
}
}
private String method;
private URL url;
private Encoding encoding = Encoding.APPLICATION_X_WWW_FORM_URLENCODED;
private Map headers = emptyMap();
private Map arguments = emptyMap();
private Object body;
private RequestHandler requestHandler = null;
private ErrorHandler errorHandler = null;
private int connectTimeout = 0;
private int readTimeout = 0;
private Status expectedStatus = null;
private PrintStream monitorStream = null;
private String multipartBoundary = UUID.randomUUID().toString();
private static final String UTF_8 = "UTF-8";
private static final int EOF = -1;
private static final ErrorHandler defaultErrorHandler = (errorStream, contentType, statusCode) -> {
String message;
if (contentType != null && contentType.startsWith("text/")) {
TextDecoder textDecoder = new TextDecoder();
message = textDecoder.read(errorStream);
} else {
message = String.format("HTTP %d", statusCode);
}
throw new WebServiceException(message, statusCode);
};
/**
* Constructs a new web service proxy.
*
* @param method
* The HTTP method.
*
* @param url
* The resource URL.
*/
public WebServiceProxy(String method, URL url) {
if (method == null) {
throw new IllegalArgumentException();
}
if (url == null) {
throw new IllegalArgumentException();
}
this.method = method.toUpperCase();
this.url = url;
}
/**
* Returns the HTTP method.
*
* @return
* The HTTP method.
*/
public String getMethod() {
return method;
}
/**
* Returns the resource URL.
*
* @return
* The resource URL.
*/
public URL getURL() {
return url;
}
/**
* Returns the encoding used for POST requests.
*
* @return
* The encoding used for POST requests.
*/
public Encoding getEncoding() {
return encoding;
}
/**
* Sets the encoding used for POST requests.
*
* @param encoding
* The encoding used for POST requests.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setEncoding(Encoding encoding) {
if (encoding == null) {
throw new IllegalArgumentException();
}
this.encoding = encoding;
return this;
}
/**
* Returns the header map.
*
* @return
* The header map.
*/
public Map getHeaders() {
return headers;
}
/**
* Sets the header map.
*
* @param headers
* The header map.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setHeaders(Map headers) {
if (headers == null) {
throw new IllegalArgumentException();
}
this.headers = headers;
return this;
}
/**
* Returns the argument map.
*
* @return
* The argument map.
*/
public Map getArguments() {
return arguments;
}
/**
* Sets the argument map.
*
* @param arguments
* The argument map.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setArguments(Map arguments) {
if (arguments == null) {
throw new IllegalArgumentException();
}
this.arguments = arguments;
return this;
}
/**
* Returns the request body.
*
* @return
* A value representing the body content, or null
if no body has been set.
*/
public Object getBody() {
return body;
}
/**
* Sets the request body.
*
* @param body
* A value representing the body content, or null
for no body.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setBody(Object body) {
this.body = body;
return this;
}
/**
* Returns the request handler.
*
* @return
* The request handler, or null
if no request handler has been set.
*/
public RequestHandler getRequestHandler() {
return requestHandler;
}
/**
* Sets the request handler.
*
* @param requestHandler
* The request handler, or null
for the default request handler.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setRequestHandler(RequestHandler requestHandler) {
this.requestHandler = requestHandler;
return this;
}
/**
* Returns the error handler.
*
* @return
* The error handler, or null
if no error handler has been set.
*/
public ErrorHandler getErrorHandler() {
return errorHandler;
}
/**
* Sets the error handler.
*
* @param errorHandler
* The error handler, or null
for the default error handler.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
return this;
}
/**
* Returns the connect timeout.
*
* @return
* The connect timeout.
*/
public int getConnectTimeout() {
return connectTimeout;
}
/**
* Sets the connect timeout.
*
* @param connectTimeout
* The connect timeout.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
return this;
}
/**
* Returns the read timeout.
*
* @return
* The read timeout.
*/
public int getReadTimeout() {
return readTimeout;
}
/**
* Sets the read timeout.
*
* @param readTimeout
* The read timeout.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
return this;
}
/**
* Returns the expected status.
*
* @return
* The expected status.
*/
public Status getExpectedStatus() {
return expectedStatus;
}
/**
* Sets the expected status.
*
* @param expectedStatus
* The expected status.
*
* @return
* The web service proxy.
*/
public WebServiceProxy setExpectedStatus(Status expectedStatus) {
this.expectedStatus = expectedStatus;
return this;
}
/**
* Returns the monitor stream.
*
* @return
* The monitor stream, or null
if no monitor stream is set.
*/
public PrintStream getMonitorStream() {
return monitorStream;
}
/**
* Sets the monitor stream.
*
* @param monitorStream
* The monitor stream, or null
for no monitor.
*/
public WebServiceProxy setMonitorStream(PrintStream monitorStream) {
this.monitorStream = monitorStream;
return this;
}
/**
* Invokes the service operation.
*
* @param
* The result type.
*
* @return
* The result of the operation.
*
* @throws IOException
* If an exception occurs while executing the operation.
*/
public T invoke() throws IOException {
return invoke(Object.class);
}
/**
* Invokes the service operation.
*
* @param
* The result type.
*
* @param type
* The result type.
*
* @return
* The result of the operation.
*
* @throws IOException
* If an exception occurs while executing the operation.
*/
public T invoke(Type type) throws IOException {
return invoke((inputStream, contentType) -> {
JSONDecoder jsonDecoder = new JSONDecoder();
return BeanAdapter.coerce(jsonDecoder.read(inputStream), type);
});
}
/**
* Invokes the service operation.
*
* @param
* The result type.
*
* @param responseHandler
* The response handler.
*
* @return
* The result of the operation.
*
* @throws IOException
* If an exception occurs while executing the operation.
*/
public T invoke(ResponseHandler extends T> responseHandler) throws IOException {
if (responseHandler == null) {
throw new IllegalArgumentException();
}
URL url;
RequestHandler requestHandler;
if (method.equals("POST") && body == null && this.requestHandler == null) {
url = this.url;
requestHandler = new RequestHandler() {
@Override
public String getContentType() {
String contentType;
switch (encoding) {
case APPLICATION_X_WWW_FORM_URLENCODED: {
contentType = "application/x-www-form-urlencoded";
break;
}
case MULTIPART_FORM_DATA: {
contentType = String.format("multipart/form-data; boundary=%s", multipartBoundary);
break;
}
default: {
throw new UnsupportedOperationException();
}
}
return contentType;
}
@Override
public void encodeRequest(OutputStream outputStream) throws IOException {
switch (encoding) {
case APPLICATION_X_WWW_FORM_URLENCODED: {
encodeApplicationXWWWFormURLEncodedRequest(outputStream);
break;
}
case MULTIPART_FORM_DATA: {
encodeMultipartFormDataRequest(outputStream);
break;
}
default: {
throw new UnsupportedOperationException();
}
}
}
};
} else {
String query = encodeQuery();
if (query.length() == 0) {
url = this.url;
} else {
url = new URL(this.url.getProtocol(), this.url.getHost(), this.url.getPort(), this.url.getFile() + "?" + query);
}
if (body != null) {
requestHandler = new RequestHandler() {
@Override
public String getContentType() {
return "application/json";
}
@Override
public void encodeRequest(OutputStream outputStream) throws IOException {
JSONEncoder jsonEncoder = new JSONEncoder();
jsonEncoder.write(BeanAdapter.adapt(body), outputStream);
}
};
} else {
requestHandler = this.requestHandler;
}
}
if (monitorStream != null) {
monitorStream.println(String.format("%s %s", method, url));
}
// Open URL connection
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod(method);
connection.setConnectTimeout(connectTimeout);
connection.setReadTimeout(readTimeout);
// Set standard headers
connection.setRequestProperty("Accept", "*/*");
Locale locale = Locale.getDefault();
connection.setRequestProperty("Accept-Language", String.format("%s-%s",
locale.getLanguage().toLowerCase(),
locale.getCountry().toLowerCase()));
// Apply custom headers
for (Map.Entry entry : headers.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key == null || value == null) {
continue;
}
connection.setRequestProperty(key, value.toString());
}
// Write request body
if (requestHandler != null) {
connection.setDoOutput(true);
String contentType = requestHandler.getContentType();
if (contentType == null) {
contentType = "application/octet-stream";
}
connection.setRequestProperty("Content-Type", contentType);
try (OutputStream outputStream = new MonitoredOutputStream(connection.getOutputStream())) {
requestHandler.encodeRequest(outputStream);
} finally {
if (monitorStream != null) {
monitorStream.println();
monitorStream.flush();
}
}
}
// Read response
int statusCode = connection.getResponseCode();
if (monitorStream != null) {
monitorStream.println(String.format("HTTP %d", statusCode));
}
String contentType = connection.getContentType();
T result;
if (statusCode / 100 == 2) {
if (expectedStatus != null && expectedStatus.code != statusCode) {
throw new WebServiceException("Unexpected status.", statusCode);
}
if (statusCode % 100 < 4) {
try (InputStream inputStream = new MonitoredInputStream(connection.getInputStream())) {
result = responseHandler.decodeResponse(inputStream, contentType);
} finally {
if (monitorStream != null) {
monitorStream.println();
monitorStream.flush();
}
}
} else {
result = null;
}
} else {
ErrorHandler errorHandler = this.errorHandler;
if (errorHandler == null) {
errorHandler = defaultErrorHandler;
}
try (InputStream errorStream = connection.getErrorStream()) {
errorHandler.handleResponse((errorStream == null) ? null : new MonitoredInputStream(errorStream), contentType, statusCode);
} finally {
if (monitorStream != null) {
monitorStream.println();
monitorStream.flush();
}
}
return null;
}
return result;
}
private String encodeQuery() throws UnsupportedEncodingException {
StringBuilder queryBuilder = new StringBuilder(256);
int i = 0;
for (Map.Entry entry : arguments.entrySet()) {
String key = entry.getKey();
if (key == null) {
continue;
}
for (Object value : getParameterValues(entry.getValue())) {
if (value == null) {
continue;
}
if (i > 0) {
queryBuilder.append("&");
}
queryBuilder.append(URLEncoder.encode(key, UTF_8));
queryBuilder.append("=");
queryBuilder.append(URLEncoder.encode(value.toString(), UTF_8));
i++;
}
}
return queryBuilder.toString();
}
private void encodeApplicationXWWWFormURLEncodedRequest(OutputStream outputStream) throws IOException {
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
writer.append(encodeQuery());
writer.flush();
}
private void encodeMultipartFormDataRequest(OutputStream outputStream) throws IOException {
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
for (Map.Entry entry : arguments.entrySet()) {
String name = entry.getKey();
if (name == null) {
continue;
}
for (Object value : getParameterValues(entry.getValue())) {
if (value == null) {
continue;
}
writer.append(String.format("--%s\r\n", multipartBoundary));
writer.append(String.format("Content-Disposition: form-data; name=\"%s\"", name));
if (value instanceof URL) {
String path = ((URL)value).getPath();
String filename = path.substring(path.lastIndexOf('/') + 1);
writer.append(String.format("; filename=\"%s\"\r\n", filename));
writer.append("Content-Type: application/octet-stream\r\n\r\n");
writer.flush();
try (InputStream inputStream = ((URL)value).openStream()) {
int b;
while ((b = inputStream.read()) != EOF) {
outputStream.write(b);
}
}
} else {
writer.append("\r\n\r\n");
writer.append(value.toString());
}
writer.append("\r\n");
}
}
writer.append(String.format("--%s--\r\n", multipartBoundary));
writer.flush();
}
@SuppressWarnings("unchecked")
private static Iterable getParameterValues(Object argument) {
Iterable values;
if (argument instanceof Iterable>) {
values = (Iterable)getParameterValue(argument);
} else {
values = singletonList(getParameterValue(argument));
}
return values;
}
private static Object getParameterValue(Object argument) {
if (argument instanceof Date) {
return ((Date)argument).getTime();
} else {
return argument;
}
}
/**
* Creates a web service proxy representing a GET request.
*
* @param url
* The resource URL.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy get(URL url) {
return new WebServiceProxy("GET", url);
}
/**
* Creates a web service proxy representing a GET request.
*
* @param baseURL
* The base URL.
*
* @param path
* The path to the resource, relative to the base URL.
*
* @param args
* Path format specifier arguments.
*
* @throws MalformedURLException
* If a URL cannot be constructed from the base URL and path.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy get(URL baseURL, String path, Object... args) throws MalformedURLException {
return get(new URL(baseURL, String.format(path, args)));
}
/**
* Creates a web service proxy representing a POST request.
*
* @param url
* The resource URL.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy post(URL url) {
return new WebServiceProxy("POST", url);
}
/**
* Creates a web service proxy representing a POST request.
*
* @param baseURL
* The base URL.
*
* @param path
* The path to the resource, relative to the base URL.
*
* @param args
* Path format specifier arguments.
*
* @throws MalformedURLException
* If a URL cannot be constructed from the base URL and path.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy post(URL baseURL, String path, Object... args) throws MalformedURLException {
return post(new URL(baseURL, String.format(path, args)));
}
/**
* Creates a web service proxy representing a PUT request.
*
* @param url
* The resource URL.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy put(URL url) {
return new WebServiceProxy("PUT", url);
}
/**
* Creates a web service proxy representing a PUT request.
*
* @param baseURL
* The base URL.
*
* @param path
* The path to the resource, relative to the base URL.
*
* @param args
* Path format specifier arguments.
*
* @throws MalformedURLException
* If a URL cannot be constructed from the base URL and path.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy put(URL baseURL, String path, Object... args) throws MalformedURLException {
return put(new URL(baseURL, String.format(path, args)));
}
/**
* Creates a web service proxy representing a DELETE request.
*
* @param url
* The resource URL.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy delete(URL url) {
return new WebServiceProxy("DELETE", url);
}
/**
* Creates a web service proxy representing a DELETE request.
*
* @param baseURL
* The base URL.
*
* @param path
* The path to the resource, relative to the base URL.
*
* @param args
* Path format specifier arguments.
*
* @throws MalformedURLException
* If a URL cannot be constructed from the base URL and path.
*
* @return
* The new web service proxy.
*/
public static WebServiceProxy delete(URL baseURL, String path, Object... args) throws MalformedURLException {
return delete(new URL(baseURL, String.format(path, args)));
}
}