more docs

This commit is contained in:
Adam D. Ruppe 2017-03-15 17:23:33 -04:00
parent 5fb4b0e6d2
commit a76dd66945
4 changed files with 263 additions and 31 deletions

5
http.d
View File

@ -1,3 +1,8 @@
/++
Old version of my http implementation.
I no longer work on this, use http2.d instead.
+/
module arsd.http;
import std.socket;

123
http2.d
View File

@ -1,7 +1,37 @@
/// HTTP client lib
// Copyright 2013, Adam D. Ruppe.
// Copyright 2013-2017, Adam D. Ruppe.
/++
This is version 2 of my http/1.1 client implementation.
It has no dependencies for basic operation, but does require OpenSSL
libraries (or compatible) to be support HTTPS. Compile with
`-version=with_openssl` to enable such support.
http2.d, despite its name, does NOT implement HTTP/2.0, but this
shouldn't matter for 99.9% of usage, since all servers will continue
to support HTTP/1.1 for a very long time.
+/
module arsd.http2;
/++
Demonstrates core functionality, using the [HttpClient],
[HttpRequest] (returned by [HttpClient.navigateTo|client.navigateTo]),
and [HttpResponse] (returned by [HttpRequest.waitForCompletion|request.waitForCompletion]).
+/
unittest {
import arsd.http2;
void main() {
auto client = new HttpClient();
auto request = client.navigateTo(Uri("http://dlang.org/"));
auto response = request.waitForCompletion();
string returnedHtml = response.contentText;
}
}
// FIXME: multipart encoded file uploads needs implementation
// future: do web client api stuff
@ -89,15 +119,32 @@ struct HttpResponse {
string statusLine; ///
string contentType; ///
string contentType; /// The content type header
string[string] cookies; ///
string[string] cookies; /// Names and values of cookies set in the response.
string[] headers; ///
string[] headers; /// Array of all headers returned.
string[string] headersHash; ///
ubyte[] content; ///
string contentText; ///
ubyte[] content; /// The raw content returned in the response body.
string contentText; /// [content], but casted to string (for convenience)
/++
returns `new Document(this.contentText)`. Requires [arsd.dom].
+/
auto contentDom()() {
import arsd.dom;
return new Document(this.contentText);
}
/++
returns `var.fromJson(this.contentText)`. Requires [arsd.jsvar].
+/
auto contentJson()() {
import arsd.jsvar;
return var.fromJson(this.contentText);
}
HttpRequestParameters requestParameters; ///
@ -1140,14 +1187,14 @@ interface ICache {
HttpResponse* getCachedResponse(HttpRequestParameters request);
}
/// Provides caching behavior similar to a real web browser
// / Provides caching behavior similar to a real web browser
class HttpCache : ICache {
HttpResponse* getCachedResponse(HttpRequestParameters request) {
return null;
}
}
/// Gives simple maximum age caching, ignoring the actual http headers
// / Gives simple maximum age caching, ignoring the actual http headers
class SimpleCache : ICache {
HttpResponse* getCachedResponse(HttpRequestParameters request) {
return null;
@ -1318,7 +1365,42 @@ version(use_openssl) {
}
/++
An experimental component for working with REST apis. Note that it
is a zero-argument template, so to create one, use `new HttpApiClient!()(args..)`
or you will get "HttpApiClient is used as a type" compile errors.
This will probably not work for you yet, and I might change it significantly.
Requires [arsd.jsvar].
Here's a snippet to create a pull request on GitHub to Phobos:
---
auto github = new HttpApiClient!()("https://api.github.com/", "your personal api token here");
// create the arguments object
// see: https://developer.github.com/v3/pulls/#create-a-pull-request
var args = var.emptyObject;
args.title = "My Pull Request";
args.head = "yourusername:" ~ branchName;
args.base = "master";
// note it is ["body"] instead of .body because `body` is a D keyword
args["body"] = "My cool PR is opened by the API!";
args.maintainer_can_modify = true;
// this translates to `repos/dlang/phobos/pulls` and sends a POST request,
// containing `args` as json, then immediately grabs the json result and extracts
// the value `html_url` from it. `prUrl` is typed `var`, from arsd.jsvar.
auto prUrl = github.rest.repos.dlang.phobos.pulls.POST(args).result.html_url;
writeln("Created: ", prUrl);
---
Why use this instead of just building the URL? Well, of course you can! This just makes
it a bit more convenient than string concatenation and manages a few headers for you.
Subtypes could potentially add static type checks too.
+/
class HttpApiClient() {
import arsd.jsvar;
@ -1331,7 +1413,13 @@ class HttpApiClient() {
string oauth2Token;
string submittedContentType;
///
/++
Params:
urlBase = The base url for the api. Tends to be something like `https://api.example.com/v2/` or similar.
oauth2Token = the authorization token for the service. You'll have to get it from somewhere else.
submittedContentType = the content-type of POST, PUT, etc. bodies.
+/
this(string urlBase, string oauth2Token, string submittedContentType = "application/json") {
httpClient = new HttpClient();
@ -1374,6 +1462,7 @@ class HttpApiClient() {
alias request this;
}
///
HttpRequestWrapper request(string uri, HttpVerb requestMethod = HttpVerb.GET, ubyte[] bodyBytes = null) {
if(uri[0] == '/')
uri = uri[1 .. $];
@ -1389,6 +1478,7 @@ class HttpApiClient() {
return HttpRequestWrapper(this, req);
}
///
var throwOnError(HttpResponse res) {
if(res.code < 200 || res.code >= 300)
throw new Exception(res.codeText);
@ -1401,6 +1491,7 @@ class HttpApiClient() {
return response;
}
///
@property RestBuilder rest() {
return RestBuilder(this, null, null);
}
@ -1409,6 +1500,7 @@ class HttpApiClient() {
// gives: "/room/Tech%20Team/history"
//
// hipchat.rest.room["Tech Team"].history("page", "12)
///
static struct RestBuilder {
HttpApiClientType apiClient;
string[] pathParts;
@ -1419,24 +1511,30 @@ class HttpApiClient() {
this.queryParts = queryParts;
}
///
RestBuilder opDispatch(string str)() {
return RestBuilder(apiClient, pathParts ~ str, queryParts);
}
///
RestBuilder opIndex(string str) {
return RestBuilder(apiClient, pathParts ~ str, queryParts);
}
///
RestBuilder opIndex(var str) {
return RestBuilder(apiClient, pathParts ~ str.get!string, queryParts);
}
///
RestBuilder opIndex(int i) {
return RestBuilder(apiClient, pathParts ~ to!string(i), queryParts);
}
///
RestBuilder opCall(T)(string name, T value) {
return RestBuilder(apiClient, pathParts, queryParts ~ [name, to!string(value)]);
}
///
string toUri() {
import std.uri;
string result;
@ -1456,12 +1554,17 @@ class HttpApiClient() {
return result;
}
///
final HttpRequestWrapper GET() { return _EXECUTE(HttpVerb.GET, this.toUri(), null); }
/// ditto
final HttpRequestWrapper DELETE() { return _EXECUTE(HttpVerb.DELETE, this.toUri(), null); }
// need to be able to send: JSON, urlencoded, multipart/form-data, and raw stuff.
/// ditto
final HttpRequestWrapper POST(T...)(T t) { return _EXECUTE(HttpVerb.POST, this.toUri(), toBytes(t)); }
/// ditto
final HttpRequestWrapper PATCH(T...)(T t) { return _EXECUTE(HttpVerb.PATCH, this.toUri(), toBytes(t)); }
/// ditto
final HttpRequestWrapper PUT(T...)(T t) { return _EXECUTE(HttpVerb.PUT, this.toUri(), toBytes(t)); }
private ubyte[] toBytes(T...)(T t) {

View File

@ -498,6 +498,7 @@ private var _op(alias _this, alias this2, string op, T)(T t) if(op != "~") {
}
///
struct var {
public this(T)(T t) {
static if(is(T == var))

165
script.d
View File

@ -1,23 +1,13 @@
/**
/++
A small script interpreter that is easily embedded inside and has easy two-way interop with the host D program.
The script language it implements is based on a hybrid of D and Javascript.
FIXME: prettier stack trace when sent to D
The interpreter is slightly buggy and poorly documented, but the basic functionality works well and much of
your existing knowledge from Javascript will carry over, making it hopefully easy to use right out of the box.
See the [#examples] to quickly get the feel of the script language as well as the interop.
FIXME: interpolated string: "$foo" or "#{expr}" or something.
FIXME: support more escape things in strings like \n, \t etc.
FIXME: add easy to use premade packages for the global object.
FIXME: maybe simplify the json!q{ } thing a bit.
FIXME: the debugger statement from javascript might be cool to throw in too.
FIXME: add continuations or something too
FIXME: Also ability to get source code for function something so you can mixin.
Script features:
FIXME: add COM support on Windows
Script_features:
OVERVIEW
* easy interop with D thanks to arsd.jsvar. When interpreting, pass a var object to use as globals.
@ -31,6 +21,7 @@
* string literals come in "foo" or 'foo', like Javascript, or `raw string` like D. Also come as nested double quotes are an option!
* mixin aka eval (does it at runtime, so more like eval than mixin, but I want it to look like D)
* scope guards, like in D
* Built-in assert() which prints its source and its arguments
* try/catch/finally/throw
You can use try as an expression without any following catch to return the exception:
@ -133,12 +124,45 @@
FIXME:
* make sure superclass ctors are called
FIXME: prettier stack trace when sent to D
FIXME: interpolated string: "$foo" or "#{expr}" or something.
FIXME: support more escape things in strings like \n, \t etc.
FIXME: add easy to use premade packages for the global object.
FIXME: maybe simplify the json!q{ } thing a bit.
FIXME: the debugger statement from javascript might be cool to throw in too.
FIXME: add continuations or something too
FIXME: Also ability to get source code for function something so you can mixin.
FIXME: add COM support on Windows
Might be nice:
varargs
lambdas
*/
lambdas - maybe without function keyword and the x => foo syntax from D.
+/
module arsd.script;
/++
+/
unittest {
var globals = var.emptyObject;
q{
function foo() {
return 13;
}
var a = foo() + 12;
assert(a == 25);
}.interpret(globals);
}
public import arsd.jsvar;
import std.stdio;
@ -149,24 +173,29 @@ import std.json;
import std.array;
import std.range;
/***************************************
/* **************************************
script to follow
****************************************/
/// Thrown on script syntax errors and the sort.
class ScriptCompileException : Exception {
this(string msg, int lineNumber, string file = __FILE__, size_t line = __LINE__) {
super(to!string(lineNumber) ~ ": " ~ msg, file, line);
}
}
/// Thrown on things like interpretation failures.
class ScriptRuntimeException : Exception {
this(string msg, int lineNumber, string file = __FILE__, size_t line = __LINE__) {
super(to!string(lineNumber) ~ ": " ~ msg, file, line);
}
}
/// This represents an exception thrown by `throw x;` inside the script as it is interpreted.
class ScriptException : Exception {
///
var payload;
///
int lineNumber;
this(var payload, int lineNumber, string file = __FILE__, size_t line = __LINE__) {
this.payload = payload;
@ -570,6 +599,10 @@ class Expression {
return obj;
}
string toInterpretedString(PrototypeObject sc) {
return toString();
}
}
class MixinExpression : Expression {
@ -899,6 +932,10 @@ class BinaryExpression : Expression {
return e1.toString() ~ " " ~ op ~ " " ~ e2.toString();
}
override string toInterpretedString(PrototypeObject sc) {
return e1.toInterpretedString(sc) ~ " " ~ op ~ " " ~ e2.toInterpretedString(sc);
}
this(string op, Expression e1, Expression e2) {
this.op = op;
this.e1 = e1;
@ -1004,6 +1041,10 @@ class VariableExpression : Expression {
return identifier;
}
override string toInterpretedString(PrototypeObject sc) {
return getVar(sc).get!string;
}
ref var getVar(PrototypeObject sc, bool recurse = true) {
return sc._getMember(identifier, true /* FIXME: recurse?? */, true);
}
@ -1491,6 +1532,25 @@ class ParentheticalExpression : Expression {
}
}
class AssertKeyword : Expression {
ScriptToken token;
this(ScriptToken token) {
this.token = token;
}
override string toString() {
return "assert";
}
override InterpretResult interpret(PrototypeObject sc) {
if(AssertKeywordObject is null)
AssertKeywordObject = new PrototypeObject();
var dummy;
dummy._object = AssertKeywordObject;
return InterpretResult(dummy, sc);
}
}
PrototypeObject AssertKeywordObject;
PrototypeObject DefaultArgumentDummyObject;
class CallExpression : Expression {
@ -1513,6 +1573,22 @@ class CallExpression : Expression {
}
override InterpretResult interpret(PrototypeObject sc) {
if(auto asrt = cast(AssertKeyword) func) {
auto assertExpression = arguments[0];
Expression assertString;
if(arguments.length > 1)
assertString = arguments[1];
var v = assertExpression.interpret(sc).value;
if(!v)
throw new ScriptException(
var(this.toString() ~ " failed, got: " ~ assertExpression.toInterpretedString(sc)),
asrt.token.lineNumber);
return InterpretResult(v, sc);
}
auto f = func.interpret(sc).value;
bool isMacro = (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null));
var[] args;
@ -1535,8 +1611,9 @@ class CallExpression : Expression {
var _this;
if(auto dve = cast(DotVarExpression) func) {
_this = dve.e1.interpret(sc).value;
} else if(auto ide = cast(IndexExpression) func)
} else if(auto ide = cast(IndexExpression) func) {
_this = ide.interpret(sc).value;
}
return InterpretResult(f.apply(_this, args), sc);
}
@ -2265,6 +2342,13 @@ Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, strin
case ScriptToken.Type.keyword:
case ScriptToken.Type.symbol:
switch(token.str) {
// assert
case "assert":
tokens.popFront();
return parseFunctionCall(tokens, new AssertKeyword(token));
break;
// declarations
case "var":
return parseVariableDeclaration(tokens, ";");
@ -2480,18 +2564,57 @@ var interpret(string code, PrototypeObject variables, string scriptFilename = nu
return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables);
}
/++
This is likely your main entry point to the interpreter. It will interpret the script code
given, with the given global variable object (which will be modified by the script, meaning
you can pass it to subsequent calls to `interpret` to store context), and return the result
of the last expression given.
---
var globals = var.emptyObject; // the global object must be an object of some type
globals.x = 10;
globals.y = 15;
// you can also set global functions through this same style, etc
var result = interpret(`x + y`, globals);
assert(result == 25);
---
$(TIP
If you want to just call a script function, interpret the definition of it,
then just call it through the `globals` object you passed to it.
---
var globals = var.emptyObject;
interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals);
var result = globals.foo()("world");
assert(result == "hello, world!");
---
)
Params:
code = the script source code you want to interpret
scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one.
variables = The global object of the script context. It will be modified by the user script.
Returns:
the result of the last expression evaluated by the script engine
+/
var interpret(string code, var variables = null, string scriptFilename = null) {
return interpretStream(
lexScript(repeat(code, 1), scriptFilename),
(variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject());
}
///
var interpretFile(File file, var globals) {
import std.algorithm;
return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name),
(globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject());
}
///
void repl(var globals) {
import std.stdio;
import std.algorithm;