Skip to content
187 changes: 129 additions & 58 deletions src/org/rascalmpl/library/Content.rsc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Content

import lang::json::IO;
import IO;

@synopsis{Content wraps the HTTP Request/Response API to support interactive visualization types
on the terminal.}
Expand Down Expand Up @@ -39,6 +40,36 @@ data Content
| content(Response response, str title="*static content*", ViewColumn viewColumn = normalViewColumn(1))
;

@synopsis{A static map with default MIME interpretations for particular file extensions.}
public map[str extension, str mimeType] mimeTypes = (
"json" :"application/json",
"css" : "text/css",
"htm" : "text/html",
"html" : "text/html",
"xml" : "text/xml",
"java" : "text/x-java-source, text/java",
"txt" : "text/plain",
"asc" : "text/plain",
"ico" : "image/x-icon",
"gif" : "image/gif",
"jpg" : "image/jpeg",
"jpeg" : "image/jpeg",
"png" : "image/png",
"mp3" : "audio/mpeg",
"m3u" : "audio/mpeg-url",
"mp4" : "video/mp4",
"ogv" : "video/ogg",
"flv" : "video/x-flv",
"mov" : "video/quicktime",
"swf" : "application/x-shockwave-flash",
"js" : "application/javascript",
"pdf" : "application/pdf",
"doc" : "application/msword",
"ogg" : "application/x-ogg",
"zip" : "application/octet-stream",
"exe" : "application/octet-stream",
"class" : "application/octet-stream"
);

@synopsis{Directly serve a static html page}
Content html(str html) = content(response(html));
Expand All @@ -51,9 +82,6 @@ Content file(loc src) = content(response(src));
@synopsis{Directly serve the contents of a string as plain text}
Content plainText(str text) = content(plain(text));

alias Body = value (type[value] expected);


@synopsis{Request values represent what a browser is asking for, most importantly the URL path.}
@description{
A request value also contains the full HTTP headers, the URL parameters as a `map[str,str]`
Expand All @@ -71,22 +99,77 @@ data Request (map[str, str] headers = (), map[str, str] parameters = (), map[str
| head(str path)
;


@synopsis{A response encodes what is send back from the server to the browser client.}
@description{
The three kinds of responses, encode either content that is already a `str`,
some file which is streamed directly from its source location or a jsonResponse
which involves a handy, automatic, encoding of Rascal values into json values.
}
data Response
= response(Status status, str mimeType, map[str,str] header, str content)
| fileResponse(loc file, str mimeType, map[str,str] header)
| jsonResponse(Status status, map[str,str] header, value val, str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'", JSONFormatter[value] formatter = str (value _) { fail; },
bool explicitConstructorNames=false, bool explicitDataTypes=false, bool dateTimeAsInt=true, bool rationalsAsString=false)
data Response = response(Status, str mimeType, map[str, str] header, Body body);

@synopsis{Bodies can be sent or received, depending on the context (client or)}
@description{
* put and post requests, when received by a server, receive bodies.
* put and post requests, when fetched by a client, sent bodies.
* a response y a server sends a body.
* a response that was fetched by a client receives a body.

The ((BodyKind)) encodes what we expect from the sender when it
puts the value onto the socket, and what we expect from the receiver
when it reads the contents off the socket. This is where builtin
conversions (formatters, parsers and validators) are activated on
the bridge between Rascal and the HTTP protocol.
}
data Body
= send(BodyKind kind, value source)
| receive(&T (BodyKind kind, type[&T] expect) receiver)
;

@synopsis{The type's of ((Body)) that we are sending or expecting to receive}
@description{
This interface bridges Rascal data to the HTTP protocol. Typically large input
such as (composite) strings and JSON code is _streamed_ onto the HTTP socket.
}
data BodyKind
= text()
| json(JSONOptions options=jsonOptions())
| file(loc storage=|unknown:///|)
;

data JSONOptions
= jsonOptions(
str dateTimeFormat = "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'",
JSONFormatter[value] formatter = str (value _) { fail; },
bool explicitConstructorNames=false,
bool explicitDataTypes=false,
bool dateTimeAsInt=true,
bool rationalsAsString=false
);

@synopsis{Convenience function for construction a JSON response value}
Response jsonResponse(Status status, map[str,str] header, value val, JSONOptions options = jsonOptions())
= response(status, "application/json", header, send(json(options=options), val));

@synopsis{Convenience function for construction a text response value}
Response response(Status status, str mimeType, map[str,str] header, str content)
= response(status, mimeType, header, send(text(), content));

@synopsis{Convenience function for file response value}
Response fileResponse(loc source, str mimeType, map[str,str] header)
= exists(source)
? response(ok(), mimeType, header, send(file(), source))
: response(notFound(), "text/plain", (), send(text(), "<file> not found."))
;

@synopsis{Convenience function for construction a file response value with automatic mimetype}
Response fileResponse(loc source, map[str,str] header)
= exists(source)
? response(ok(), mimeTypes[source.extension]?"text/plain", header, send(file(), source))
: response(notFound(), "text/plain", (), send(text(), "<source> not found."))
;

@synopsis{Utility to quickly render a string as HTML content}
Response response(str content, map[str,str] header = ()) = response(ok(), "text/html", header, content);
Response response(str content, map[str,str] header = ()) = response(ok(), "text/html", header, send(text(), content));

@synopsis{Utility to quickly report an HTTP error with a user-defined message}
Response response(Status status, str explanation, map[str,str] header = ()) = response(status, "text/plain", header, explanation);
Expand All @@ -108,56 +191,44 @@ default Response response(value val, map[str,str] header = ()) = jsonResponse(o
@benefits{
Fast way of producing JSON strings for embedded DSLs on the Rascal side.
}
Response response(value val, JSONFormatter[value] formatter, map[str,str] header = ()) = jsonResponse(ok(), header, val, formatter=formatter);
Response response(value val, JSONFormatter[value] formatter, map[str,str] header = ()) = jsonResponse(ok(), header, val, options=jsonOptions(formatter=formatter));

@synopsis{Encoding of HTTP status}
data Status
= ok()
| created()
| accepted()
| noContent()
| partialContent()
| redirect()
| notModified()
| badRequest()
| unauthorized()
| forbidden()
| notFound()
| rangeNotSatisfiable()
| internalError()
;

data Status
= ok()
| notFound()
| accepted()
| badRequest()
| conflict()
| created()
| expectationFailed()
| forbidden()
| found()
| gone()
| internalError()
| lengthRequired()
| methodNotAllowed()
| multiStatus()
| notAcceptible()
| notImplemented()
| notModified()
| noContent()
| partialContent()
| payloadTooLarge()
| preconditionFailed()
| rangeNotSatisfiable()
| redirect()
| redirectSeeOther()
| requestTimeout()
| serviceUnavailable()
| switchProtocol()
| temporaryRedirect()
| tooManyRequests()
| unauthorized()
| unsupportedHTTPVersion()
| unsupportedMediaType()
;

@synopsis{A static map with default MIME interpretations for particular file extensions.}
public map[str extension, str mimeType] mimeTypes = (
"json" :"application/json",
"css" : "text/css",
"htm" : "text/html",
"html" : "text/html",
"xml" : "text/xml",
"java" : "text/x-java-source, text/java",
"txt" : "text/plain",
"asc" : "text/plain",
"ico" : "image/x-icon",
"gif" : "image/gif",
"jpg" : "image/jpeg",
"jpeg" : "image/jpeg",
"png" : "image/png",
"mp3" : "audio/mpeg",
"m3u" : "audio/mpeg-url",
"mp4" : "video/mp4",
"ogv" : "video/ogg",
"flv" : "video/x-flv",
"mov" : "video/quicktime",
"swf" : "application/x-shockwave-flash",
"js" : "application/javascript",
"pdf" : "application/pdf",
"doc" : "application/msword",
"ogg" : "application/x-ogg",
"zip" : "application/octet-stream",
"exe" : "application/octet-stream",
"class" : "application/octet-stream"
);

@synopsis{Hint the IDE where to open the next web view or editor}
@description{
Expand Down
Loading
Loading