Skip to content

Commit

Permalink
Features: Allow IXI Modules to dictate response's content-type. (iota…
Browse files Browse the repository at this point in the history
…ledger#743)

Updates old feature pull request improving X-IOTA-API-Version check management
  • Loading branch information
Patrick Jusic committed Mar 20, 2019
1 parent d4f2a50 commit bb852b4
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 17 deletions.
88 changes: 71 additions & 17 deletions src/main/java/com/iota/iri/service/API.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnio.channels.StreamSinkChannel;
import org.xnio.channels.StreamSourceChannel;
import org.xnio.streams.ChannelInputStream;

import java.io.IOException;
Expand Down Expand Up @@ -88,7 +89,7 @@ public class API {

private Undertow server;

private final Gson gson = new GsonBuilder().create();
private final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
private volatile PearlDiver pearlDiver = new PearlDiver();

private final AtomicInteger counter = new AtomicInteger(0);
Expand Down Expand Up @@ -220,7 +221,6 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
*/
private void sendResponse(HttpServerExchange exchange, AbstractResponse res, long beginningTime) throws IOException {
res.setDuration((int) (System.currentTimeMillis() - beginningTime));
final String response = gson.toJson(res);

if (res instanceof ErrorResponse) {
// bad request or invalid parameters
Expand All @@ -233,7 +233,10 @@ private void sendResponse(HttpServerExchange exchange, AbstractResponse res, lon
exchange.setStatusCode(500);
}

setupResponseHeaders(exchange);

setupResponseHeaders(exchange, res);

final String response = convertResponseToClientFormat(res);

ByteBuffer responseBuf = ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8));
exchange.setResponseContentLength(responseBuf.array().length);
Expand All @@ -258,6 +261,21 @@ private void sendResponse(HttpServerExchange exchange, AbstractResponse res, lon
sinkChannel.resumeWrites();
}

private String convertResponseToClientFormat(AbstractResponse res) {
String response = null;
if(res instanceof IXIResponse){
final String content = ((IXIResponse)res).getContent();
if(content != null && StringUtils.isNotBlank(content)){
response = content;
}
}
if(response == null){
response = gson.toJson(res);
}

return response;
}

/**
* <p>
* Processes an API HTTP request.
Expand All @@ -275,23 +293,48 @@ private void sendResponse(HttpServerExchange exchange, AbstractResponse res, lon
* @throws IOException If the body of this HTTP request cannot be read
*/
private void processRequest(final HttpServerExchange exchange) throws IOException {
final ChannelInputStream cis = new ChannelInputStream(exchange.getRequestChannel());
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");

final long beginningTime = System.currentTimeMillis();
final String body = IotaIOUtils.toString(cis, StandardCharsets.UTF_8);
AbstractResponse response;

if (!exchange.getRequestHeaders().contains("X-IOTA-API-Version")) {
response = ErrorResponse.create("Invalid API Version");
} else if (body.length() > maxBodyLength) {
response = ErrorResponse.create("Request too long");
} else {
response = process(body, exchange.getSourceAddress());
AbstractResponse response;
try {
final String body = getRequestBody(exchange);
if (body.length() > maxBodyLength) {
response = ErrorResponse.create("Request too long");
} else {
response = process(body, exchange);
}
} catch (IOException e) {
log.error("API Exception: {}", e.getLocalizedMessage(), e);
response = ErrorResponse.create(e.getLocalizedMessage());
}
sendResponse(exchange, response, beginningTime);
}

private String getRequestBody(final HttpServerExchange exchange) throws IOException {
StreamSourceChannel requestChannel = exchange.getRequestChannel();
final ChannelInputStream cis = new ChannelInputStream(requestChannel);
String body = IotaIOUtils.toString(cis, StandardCharsets.UTF_8);

if(body.length() == 0){
body = getQueryParamsBody(exchange.getQueryParameters());
} else if (!exchange.getRequestHeaders().contains("X-IOTA-API-Version")) {
throw new IOException ("Invalid API Version");
}
return body;
}

private String getQueryParamsBody(Map<String, Deque<String>> queryParameters) {
Map<String, String> parametersMapper = new HashMap<String, String>();

for (String key : queryParameters.keySet()) {
Deque<String> dequeuedParameter = queryParameters.get(key);
String parameterValue = dequeuedParameter.getFirst();
parametersMapper.put(key, parameterValue);
}

return gson.toJson(parametersMapper);
}

/**
* Handles an API request body.
* Its returned {@link AbstractResponse} is created using the following logic
Expand Down Expand Up @@ -324,8 +367,7 @@ private void processRequest(final HttpServerExchange exchange) throws IOExceptio
* @throws UnsupportedEncodingException If the requestString cannot be parsed into a Map.
Currently caught and turned into a {@link ExceptionResponse}.
*/
private AbstractResponse process(final String requestString, InetSocketAddress sourceAddress)
throws UnsupportedEncodingException {
private AbstractResponse process(final String requestString, final HttpServerExchange exchange) throws UnsupportedEncodingException {

try {
// Request JSON data into map
Expand All @@ -346,6 +388,7 @@ private AbstractResponse process(final String requestString, InetSocketAddress s
return ErrorResponse.create("COMMAND parameter has not been specified in the request.");
}

InetSocketAddress sourceAddress = exchange.getSourceAddress();
// Is this command allowed to be run from this request address?
// We check the remote limit API configuration.
if (instance.configuration.getRemoteLimitApi().contains(command) &&
Expand Down Expand Up @@ -1624,12 +1667,23 @@ private boolean validTrytes(String trytes, int length, char zeroAllowed) {
* Updates the {@link HttpServerExchange} {@link HeaderMap} with the proper response settings.
* @param exchange Contains information about what the client has send to us
*/
private static void setupResponseHeaders(HttpServerExchange exchange) {
private static void setupResponseHeaders(final HttpServerExchange exchange, final AbstractResponse res) {
final HeaderMap headerMap = exchange.getResponseHeaders();
headerMap.add(new HttpString("Access-Control-Allow-Origin"),"*");
headerMap.add(new HttpString("Keep-Alive"), "timeout=500, max=100");
headerMap.put(Headers.CONTENT_TYPE, getResponseContentType(res));

}

private static String getResponseContentType(AbstractResponse response) {
if(response instanceof IXIResponse){
return ((IXIResponse)response).getResponseContentType();
}
else {
return "application/json";
}
}

/**
* Sets up the {@link HttpHandler} to have correct security settings.
* Remote authentication is blocked for anyone except
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/com/iota/iri/service/dto/IXIResponse.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.iota.iri.service.dto;

import com.iota.iri.IXI;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;

/**
* <p>
Expand All @@ -27,4 +29,26 @@ public static IXIResponse create(Object myixi) {
public Object getResponse() {
return ixi;
}

private String getdefaultContentType() {
return "application/json";
}

public String getResponseContentType() {
Map<String, Object> responseMapper = getResponseMapper();
String fieldObj = (String)responseMapper.get("contentType");
String fieldValue = StringUtils.isBlank(fieldObj) ? getdefaultContentType() : fieldObj;
return fieldValue;
}

private Map<String, Object> getResponseMapper(){
return (Map<String, Object>)ixi;
}

public String getContent() {
Map<String, Object> responseMapper = getResponseMapper();
String fieldObj = (String)responseMapper.get("content");
String fieldValue = StringUtils.isBlank(fieldObj) ? null : fieldObj;
return fieldValue;
}
}

0 comments on commit bb852b4

Please sign in to comment.