Skip to content

Commit

Permalink
Merge pull request #308 from cyb3r4nt/jsonrpc4j-issue-306
Browse files Browse the repository at this point in the history
#306: Add support for newer jakarta.servlet.http API into JsonRpcServer
  • Loading branch information
briandilley committed May 24, 2023
2 parents f35fc2a + 8e0beff commit 05ce039
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 23 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ dependencies {
// TODO: remove deprecated portlet-api when support is removed from the code
servletSupportImplementation 'javax.portlet:portlet-api:3.0.1'
servletSupportImplementation 'javax.servlet:javax.servlet-api:4.0.1'
// TODO: Jakarta EE 9 and jakarta.servlet-api 5.x are still compatible with Java SE 8,
// update jakarta.servlet-api to version 6+ when JDK baseline is increased to 11+
servletSupportImplementation 'jakarta.servlet:jakarta.servlet-api:5.0.0'

implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
Expand Down
197 changes: 174 additions & 23 deletions src/main/java/com/googlecode/jsonrpc4j/JsonRpcServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,14 @@
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 String contentType = JSONRPC_CONTENT_TYPE;

/**
Expand Down Expand Up @@ -92,6 +88,8 @@ public void handle(ResourceRequest request, ResourceResponse response) throws IO
OutputStream output = response.getPortletOutputStream();
handleRequest(input, output);
// fix to not flush within handleRequest() but outside so http status code can be set
// TODO: this logic may be changed to use handleCommon() method,
// HTTP status code may be provided by the HttpStatusCodeProvider extension
output.flush();
}

Expand All @@ -117,50 +115,74 @@ private static InputStream createInputStream(ResourceRequest request) throws IOE
* @throws IOException on error
*/
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.debug("Handling HttpServletRequest {}", request);
handleCommon(
new JavaxHttpServletRequest(request),
new JavaxHttpServletResponse(response)
);
}

/**
* Handles a servlet request.
*
* @param request the {@link jakarta.servlet.http.HttpServletRequest}
* @param response the {@link jakarta.servlet.http.HttpServletResponse}
* @throws IOException on error
*/
public void handle(
jakarta.servlet.http.HttpServletRequest request,
jakarta.servlet.http.HttpServletResponse response
) throws IOException {
handleCommon(
new JakartaHttpServletRequest(request),
new JakartaHttpServletResponse(response)
);
}

private void handleCommon(CommonHttpServletRequest request, CommonHttpServletResponse response) throws IOException {
logger.debug("Handling HttpServletRequest {}", request.unwrap());
response.setContentType(contentType);
OutputStream output = response.getOutputStream();
InputStream input = getRequestStream(request);
int result = ErrorResolver.JsonError.PARSE_ERROR.code;
int contentLength = 0;

ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
try {
String acceptEncoding = request.getHeader(ACCEPT_ENCODING);
result = handleRequest0(input, output, acceptEncoding, response, byteOutput);

contentLength = byteOutput.size();
result = handleRequest(input, byteOutput);
} catch (Throwable t) {
if (StreamEndedException.class.isInstance(t)) {
if (t instanceof StreamEndedException) {
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);
response.setContentLength(contentLength);

response.setStatus(resolveHttpStatusCode(result));
response.setContentLength(byteOutput.size());
byteOutput.writeTo(output);
output.flush();
}

private InputStream getRequestStream(HttpServletRequest request) throws IOException {
private int resolveHttpStatusCode(int result) {
if (this.httpStatusCodeProvider != null) {
return this.httpStatusCodeProvider.getHttpStatusCode(result);
} else {
return DefaultHttpStatusCodeProvider.INSTANCE.getHttpStatusCode(result);
}
}

private InputStream getRequestStream(CommonHttpServletRequest request) throws IOException {
InputStream input;
if (request.getMethod().equals("POST")) {
if ("POST".equals(request.getMethod())) {
input = request.getInputStream();
} else if (request.getMethod().equals("GET")) {
} else if ("GET".equals(request.getMethod())) {
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, ByteArrayOutputStream byteOutput) throws IOException {
return handleRequest(input, byteOutput);
}

private static InputStream createInputStream(HttpServletRequest request) throws IOException {
private static InputStream createInputStream(CommonHttpServletRequest request) throws IOException {
String method = request.getParameter(METHOD);
String id = request.getParameter(ID);
String params = request.getParameter(PARAMS);
Expand All @@ -175,4 +197,133 @@ public void setContentType(String contentType) {
this.contentType = contentType;
}

private interface CommonHttpServletRequest {
Object unwrap();
InputStream getInputStream() throws IOException;
String getMethod();
String getParameter(String name);
}

private static class JavaxHttpServletRequest implements CommonHttpServletRequest {

private final HttpServletRequest request;

private JavaxHttpServletRequest(HttpServletRequest request) {
this.request = request;
}

@Override
public Object unwrap() {
return this.request;
}

@Override
public InputStream getInputStream() throws IOException {
return this.request.getInputStream();
}

@Override
public String getMethod() {
return this.request.getMethod();
}

@Override
public String getParameter(String name) {
return this.request.getParameter(name);
}
}

private static class JakartaHttpServletRequest implements CommonHttpServletRequest {

private final jakarta.servlet.http.HttpServletRequest request;

private JakartaHttpServletRequest(jakarta.servlet.http.HttpServletRequest request) {
this.request = request;
}

@Override
public Object unwrap() {
return this.request;
}

@Override
public InputStream getInputStream() throws IOException {
return this.request.getInputStream();
}

@Override
public String getMethod() {
return this.request.getMethod();
}

@Override
public String getParameter(String name) {
return this.request.getParameter(name);
}
}

private interface CommonHttpServletResponse {
void setContentType(String type);
void setStatus(int sc);
void setContentLength(int len);
OutputStream getOutputStream() throws IOException;
}

private static class JavaxHttpServletResponse implements CommonHttpServletResponse {

private final HttpServletResponse response;

private JavaxHttpServletResponse(HttpServletResponse response) {
this.response = response;
}

@Override
public void setContentType(String type) {
this.response.setContentType(type);
}

@Override
public void setStatus(int sc) {
this.response.setStatus(sc);
}

@Override
public void setContentLength(int len) {
this.response.setContentLength(len);
}

@Override
public OutputStream getOutputStream() throws IOException {
return this.response.getOutputStream();
}
}

private static class JakartaHttpServletResponse implements CommonHttpServletResponse {

private final jakarta.servlet.http.HttpServletResponse response;

private JakartaHttpServletResponse(jakarta.servlet.http.HttpServletResponse response) {
this.response = response;
}

@Override
public void setContentType(String type) {
this.response.setContentType(type);
}

@Override
public void setStatus(int sc) {
this.response.setStatus(sc);
}

@Override
public void setContentLength(int len) {
this.response.setContentLength(len);
}

@Override
public OutputStream getOutputStream() throws IOException {
return this.response.getOutputStream();
}
}
}

0 comments on commit 05ce039

Please sign in to comment.