Allow browser requetsts to force a raw response

This commit is contained in:
jamesagnew 2015-09-07 21:52:35 -04:00
parent 7217458681
commit 9af3fcf02d
3 changed files with 125 additions and 50 deletions

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.apache.commons.lang3.StringUtils.isBlank;
/*
* #%L
* HAPI FHIR - Core Library
@ -39,16 +41,19 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.UrlUtil;
/**
* This interceptor detects when a request is coming from a browser, and automatically
* returns a response with syntax highlighted (coloured) HTML for the response instead
* of just returning raw XML/JSON.
* This interceptor detects when a request is coming from a browser, and automatically returns a response with syntax
* highlighted (coloured) HTML for the response instead of just returning raw XML/JSON.
*
* @since 1.0
*/
public class ResponseHighlighterInterceptor extends InterceptorAdapter {
public static final String PARAM_RAW_TRUE = "true";
public static final String PARAM_RAW = "_raw";
private String format(String theResultBody, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
@ -161,8 +166,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
}
@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws AuthenticationException {
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
/*
* It's not a browser...
@ -171,9 +175,9 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
if (highestRankedAcceptValues.contains(Constants.CT_HTML) == false) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
/*
* It's an AJAX request, so no HTML
* It's an AJAX request, so no HTML
*/
String requestedWith = theServletRequest.getHeader("X-Requested-With");
if (requestedWith != null) {
@ -186,9 +190,14 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
if (theRequestDetails.getRequestType() != RequestTypeEnum.GET) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
String[] rawParamValues = theRequestDetails.getParameters().get(PARAM_RAW);
if (rawParamValues != null && rawParamValues.length > 0 && rawParamValues[0].equals(PARAM_RAW_TRUE)) {
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
}
streamResponse(theRequestDetails, theServletResponse, theResponseObject);
return false;
}
@ -201,43 +210,67 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
p = defaultResponseEncoding.newParser(theRequestDetails.getServer().getFhirContext());
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
}
EncodingEnum encoding = p.getEncoding();
EncodingEnum encoding = p.getEncoding();
String encoded = p.encodeResourceToString(resource);
theServletResponse.setContentType(Constants.CT_HTML_WITH_UTF8);
StringBuilder rawB = new StringBuilder();
for (String next : theRequestDetails.getParameters().keySet()) {
if (next.equals(PARAM_RAW)) {
continue;
}
for (String nextValue : theRequestDetails.getParameters().get(next)) {
if (isBlank(nextValue)) {
continue;
}
if (rawB.length() == 0) {
rawB.append('?');
}else {
rawB.append('&');
}
rawB.append(UrlUtil.escape(next));
rawB.append('=');
rawB.append(UrlUtil.escape(nextValue));
}
}
if (rawB.length() == 0) {
rawB.append('?');
}else {
rawB.append('&');
}
rawB.append(PARAM_RAW).append('=').append(PARAM_RAW_TRUE);
//@formatter:on
String out = "<html lang=\"en\">\n" +
" <head>\n" +
" <meta charset=\"utf-8\" />\n" +
" <style>\n" +
".hlQuot {\n" +
" color: #88F;\n" +
"}\n" +
".hlAttr {\n" +
" color: #888;\n" +
"}\n" +
".hlTagName {\n" +
" color: #006699;\n" +
"}\n" +
".hlControl {\n" +
" color: #660000;\n" +
"}\n" +
".hlText {\n" +
" color: #000000;\n" +
"}\n" +
".hlUrlBase {\n" +
"}" +
" </style>\n" +
" </head>\n" +
"\n" +
" <body>" +
"<pre>" + format(encoded, encoding) + "</pre>" +
" </body>" +
"</html>";
//@formatter:off
String out = "<html lang=\"en\">\n" +
" <head>\n" +
" <meta charset=\"utf-8\" />\n" +
" <style>\n" + ".hlQuot {\n" +
" color: #88F;\n" + "}\n" +
".hlAttr {\n" +
" color: #888;\n" +
"}\n" + ".hlTagName {\n" +
" color: #006699;\n" + "}\n" +
".hlControl {\n" +
" color: #660000;\n" +
"}\n" + ".hlText {\n" +
" color: #000000;\n" +
"}\n" +
".hlUrlBase {\n" +
"}" +
" </style>\n" +
" </head>\n" +
"\n" +
" <body>" +
"This result is being rendered in HTML for easy viewing. <a href=\"" + rawB.toString() + "\">Click here</a> to disable this.<br/><br/>" +
"<pre>" +
format(encoded, encoding) +
"</pre>" +
" </body>" +
"</html>";
//@formatter:on
try {
theServletResponse.getWriter().append(out);
theServletResponse.getWriter().close();
@ -255,9 +288,9 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
if (!accept.contains(Constants.CT_HTML)) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
/*
* It's an AJAX request, so no HTML
* It's an AJAX request, so no HTML
*/
String requestedWith = theServletRequest.getHeader("X-Requested-With");
if (requestedWith != null) {
@ -270,15 +303,14 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
if (theRequestDetails.getRequestType() != RequestTypeEnum.GET) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
if (theException.getOperationOutcome() == null) {
return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse);
}
streamResponse(theRequestDetails, theServletResponse, theException.getOperationOutcome());
return false;
}
}

View File

@ -3,7 +3,10 @@ package ca.uhn.fhir.rest.server.interceptor;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -28,7 +31,6 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
@ -171,6 +173,40 @@ public class ResponseHighlightingInterceptorTest {
assertThat(output, stringContainsInOrder("<body>", "<pre>", "\n", "</pre>"));
}
@Test
public void testHighlightForceRaw() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
HttpServletRequest req = mock(HttpServletRequest.class);
when(req.getHeaders(Constants.HEADER_ACCEPT)).thenAnswer(new Answer<Enumeration<String>>() {
@Override
public Enumeration<String> answer(InvocationOnMock theInvocation) throws Throwable {
return new ArrayEnumeration<String>("text/html,application/xhtml+xml,application/xml;q=0.9");
}
});
HttpServletResponse resp = mock(HttpServletResponse.class);
StringWriter sw = new StringWriter();
when(resp.getWriter()).thenReturn(new PrintWriter(sw));
Patient resource = new Patient();
resource.addName().addFamily("FAMILY");
RequestDetails reqDetails = new RequestDetails();
reqDetails.setRequestType(RequestTypeEnum.GET);
HashMap<String, String[]> params = new HashMap<String, String[]>();
params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE });
params.put(Constants.PARAM_FORMAT, new String[] { Constants.CT_XML });
params.put(ResponseHighlighterInterceptor.PARAM_RAW, new String[] { ResponseHighlighterInterceptor.PARAM_RAW_TRUE });
reqDetails.setParameters(params);
reqDetails.setServer(new RestfulServer());
reqDetails.setServletRequest(req);
// true means it decided to not handle the request..
assertTrue(ic.outgoingResponse(reqDetails, resource, req, resp));
}
@Test
public void testHighlightNormalResponse() throws Exception {
ResponseHighlighterInterceptor ic = new ResponseHighlighterInterceptor();
@ -202,6 +238,7 @@ public class ResponseHighlightingInterceptorTest {
ourLog.info(output);
assertThat(output, containsString("<span class='hlTagName'>Patient</span>"));
assertThat(output, not(stringContainsInOrder("<body>", "<pre>", "\n", "</pre>")));
assertThat(output, containsString("<a href=\"?_raw=true\">"));
}
/**

View File

@ -172,6 +172,12 @@
JPA server can now store Conformance resources, per a request
from David Hay
</action>
<action type="add">
ResponseHighlightingInterceptor now skips handling responses if it
finds a URL parameter of <![CDATA[<code>_raw=true</code>]]> (in other
words, if this parameter is found, the response won't be returned as
HTML even if the request is detected as coming from a browser.
</action>
</release>
<release version="1.1" date="2015-07-13">
<action type="add">