package com.googlecode.jsonrpc4j; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * A JSON-RPC request server reads JSON-RPC requests from an input stream and writes responses to an output stream. * Supports handler and servlet requests. */ @SuppressWarnings("unused") public class JsonRpcServer extends JsonRpcBasicServer { private static final Logger logger = LoggerFactory.getLogger(JsonRpcServer.class); private static final String GZIP = "gzip"; private final boolean gzipResponses; private String contentType = JSONRPC_CONTENT_TYPE; /** * Creates the server with the given {@link ObjectMapper} delegating * all calls to the given {@code handler} {@link Object} but only * methods available on the {@code remoteInterface}. * * @param mapper the {@link ObjectMapper} * @param handler the {@code handler} * @param remoteInterface the interface * @param gzipResponses whether gzip the response that is sent to the client. */ public JsonRpcServer(ObjectMapper mapper, Object handler, Class<?> remoteInterface, boolean gzipResponses) { super(mapper, handler, remoteInterface); this.gzipResponses = gzipResponses; } /** * Creates the server with the given {@link ObjectMapper} delegating * all calls to the given {@code handler} {@link Object} but only * methods available on the {@code remoteInterface}. * * @param mapper the {@link ObjectMapper} * @param handler the {@code handler} * @param remoteInterface the interface */ public JsonRpcServer(ObjectMapper mapper, Object handler, Class<?> remoteInterface) { super(mapper, handler, remoteInterface); this.gzipResponses = false; } /** * Creates the server with the given {@link ObjectMapper} delegating * all calls to the given {@code handler}. * * @param mapper the {@link ObjectMapper} * @param handler the {@code handler} */ public JsonRpcServer(ObjectMapper mapper, Object handler) { super(mapper, handler, null); this.gzipResponses = false; } /** * Creates the server with a default {@link ObjectMapper} delegating * all calls to the given {@code handler} {@link Object} but only * methods available on the {@code remoteInterface}. * * @param handler the {@code handler} * @param remoteInterface the interface */ private JsonRpcServer(Object handler, Class<?> remoteInterface) { super(new ObjectMapper(), handler, remoteInterface); this.gzipResponses = false; } /** * Creates the server with a default {@link ObjectMapper} delegating * all calls to the given {@code handler}. * * @param handler the {@code handler} */ public JsonRpcServer(Object handler) { super(new ObjectMapper(), handler, null); this.gzipResponses = false; } /** * Handles a portlet request. * * @param request the {@link ResourceRequest} * @param response the {@link ResourceResponse} * @throws IOException on error */ public void handle(ResourceRequest request, ResourceResponse response) throws IOException { logger.debug("Handing ResourceRequest {}", request.getMethod()); response.setContentType(contentType); InputStream input = getRequestStream(request); OutputStream output = response.getPortletOutputStream(); handleRequest(input, output); // fix to not flush within handleRequest() but outside so http status code can be set output.flush(); } private InputStream getRequestStream(ResourceRequest request) throws IOException { if (request.getMethod().equals("POST")) { return request.getPortletInputStream(); } else if (request.getMethod().equals("GET")) { return createInputStream(request); } else { throw new IOException("Invalid request method, only POST and GET is supported"); } } private static InputStream createInputStream(ResourceRequest request) throws IOException { return createInputStream(request.getParameter(METHOD), request.getParameter(ID), request.getParameter(PARAMS)); } /** * Handles a servlet request. * * @param request the {@link HttpServletRequest} * @param response the {@link HttpServletResponse} * @throws IOException on error */ public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { logger.debug("Handling HttpServletRequest {}", request); response.setContentType(contentType); OutputStream output = response.getOutputStream(); InputStream input = getRequestStream(request); int result = ErrorResolver.JsonError.PARSE_ERROR.code; try { String acceptEncoding = request.getHeader(ACCEPT_ENCODING); result = handleRequest0(input, output, acceptEncoding, response); } catch (Throwable t) { if (StreamEndedException.class.isInstance(t)) { logger.debug("Bad request: empty contents!"); } else { logger.error(t.getMessage(), t); } } int httpStatusCode = httpStatusCodeProvider == null ? DefaultHttpStatusCodeProvider.INSTANCE.getHttpStatusCode(result) : httpStatusCodeProvider.getHttpStatusCode(result); response.setStatus(httpStatusCode); output.flush(); } private InputStream getRequestStream(HttpServletRequest request) throws IOException { InputStream input; if (request.getMethod().equals("POST")) { input = createInputStream(request.getInputStream(), request.getHeader(CONTENT_ENCODING)); } else if (request.getMethod().equals("GET")) { input = createInputStream(request); } else { throw new IOException("Invalid request method, only POST and GET is supported"); } return input; } private int handleRequest0(InputStream input, OutputStream output, String contentEncoding, HttpServletResponse response) throws IOException { try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) { int result = handleRequest(input, byteOutput); boolean canGzipResponse = contentEncoding != null && GZIP.equalsIgnoreCase(contentEncoding); // Use gzip if client's accept-encoding is set to gzip and gzipResponses is enabled. if (gzipResponses && canGzipResponse) { response.addHeader(CONTENT_ENCODING, GZIP); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (GZIPOutputStream gos = new GZIPOutputStream(baos)) { gos.write(byteOutput.toByteArray()); } response.setContentLength(baos.size()); output.write(baos.toByteArray()); } else { response.setContentLength(byteOutput.size()); output.write(byteOutput.toByteArray()); } return result; } } private static InputStream createInputStream(InputStream inputStream, String contentEncoding) throws IOException { InputStream input; if (contentEncoding != null && GZIP.equalsIgnoreCase(contentEncoding)) { input = new GZIPInputStream(inputStream); } else { input = inputStream; } return input; } private static InputStream createInputStream(HttpServletRequest request) throws IOException { String method = request.getParameter(METHOD); String id = request.getParameter(ID); String params = request.getParameter(PARAMS); if (method == null && id == null && params == null) { return new ByteArrayInputStream(new byte[]{}); } else { return createInputStream(method, id, params); } } public void setContentType(String contentType) { this.contentType = contentType; } }