URI uri = url.getURI();
int port = uri.getPort() != -1 ? uri.getPort() : 80;
String host = uri.getHost() != null ? uri.getHost() : "localhost";
final ICallableValue callee = (ICallableValue) callback;
NanoHTTPD server = new NanoHTTPD(host, port) {
@Override
public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms,
Map<String, String> files) {
IConstructor methodVal = makeMethod(method);
IMap headersVal = makeMap(headers);
IMap paramsVal= makeMap(parms);
IMap filesVal= makeMap(files);
ISourceLocation loc = vf.sourceLocation(URIUtil.assumeCorrect("request", "", uri));
try {
synchronized (callee.getEval()) {
callee.getEval().__setInterrupt(false);
Result<IValue> response = callee.call(argTypes, new IValue[] { loc, methodVal, headersVal, paramsVal, filesVal }, null);
return translateResponse(method, response.getValue());
}
}
catch (Throw rascalException) {
ctx.getStdErr().println(rascalException.getMessage());
return new Response(Status.INTERNAL_ERROR, "text/plain", rascalException.getMessage());
}
catch (Throwable unexpected) {
ctx.getStdErr().println(unexpected.getMessage());
unexpected.printStackTrace(ctx.getStdErr());
return new Response(Status.INTERNAL_ERROR, "text/plain", unexpected.getMessage());
}
}
private Response translateResponse(Method method, IValue value) {
IConstructor cons = (IConstructor) value;
initMethodAndStatusValues(ctx);
if (cons.getName().equals("fileResponse")) {
return translateFileResponse(method, cons);
}
else {
return translateTextResponse(method, cons);
}
}
private Response translateFileResponse(Method method, IConstructor cons) {
ISourceLocation l = (ISourceLocation) cons.get("file");
IString mimeType = (IString) cons.get("mimeType");
IMap header = (IMap) cons.get("header");
URI uri = l.getURI();
Response response;
try {
response = new Response(Status.OK, mimeType.getValue(), ctx.getResolverRegistry().getInputStream(uri));
addHeaders(response, header);
return response;
} catch (IOException e) {
e.printStackTrace(ctx.getStdErr());
return new Response(Status.NOT_FOUND, "text/plain", l + " not found.\n" + e);
}
}
private Response translateTextResponse(Method method, IConstructor cons) {
IString mimeType = (IString) cons.get("mimeType");
IMap header = (IMap) cons.get("header");
IString data = (IString) cons.get("content");
Status status = translateStatus((IConstructor) cons.get("status"));
if (method != Method.HEAD) {
switch (status) {
case BAD_REQUEST:
case UNAUTHORIZED:
case NOT_FOUND:
case FORBIDDEN:
case RANGE_NOT_SATISFIABLE:
case INTERNAL_ERROR:
if (data.length() == 0) {
data = vf.string(status.getDescription());
}
default:
break;
}
}
Response response = new Response(status, mimeType.getValue(), data.getValue());
addHeaders(response, header);
return response;
}
private void addHeaders(Response response, IMap header) {
// TODO add first class support for cache control on the Rascal side. For
// now we prevent any form of client-side caching with this.. hopefully.
response.addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.addHeader("Pragma", "no-cache");
response.addHeader("Expires", "0");
for (IValue key : header) {
response.addHeader(((IString) key).getValue(), ((IString) header.get(key)).getValue());
}
}
private Status translateStatus(IConstructor cons) {
initMethodAndStatusValues(ctx);
return statusValues.get(cons);
}
private IMap makeMap(Map<String, String> headers) {
IMapWriter writer = vf.mapWriter();
for (Entry<String, String> entry : headers.entrySet()) {
writer.put(vf.string(entry.getKey()), vf.string(entry.getValue()));
}
return writer.done();
}
private IConstructor makeMethod(Method method) {
initMethodAndStatusValues(ctx);
return methodValues.get(method);
}
};
servers.put(url, server);
try {
server.start();
} catch (IOException e) {
throw RuntimeExceptionFactory.io(vf.string(e.getMessage()), null, null);
}
}