mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
288 lines
11 KiB
C
288 lines
11 KiB
C
/*
|
|
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
|
|
*
|
|
* Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
* This file is designed to be parsed during the build process
|
|
*
|
|
* Contains JavaScript HTTP Functions
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
#include "jswrap_http.h"
|
|
#include "httpserver.h"
|
|
|
|
/*
|
|
|
|
http.createServer(function (req, res) {
|
|
console.log("Connected");
|
|
res.writeHead(200, {'Content-Type': 'text/plain'});
|
|
res.end('Hello World\n');
|
|
}).listen(8080);
|
|
|
|
*/
|
|
|
|
|
|
/*JSON{ "type":"class",
|
|
"class" : "http",
|
|
"description" : [
|
|
"This class allows you to create http servers and make http requests",
|
|
"NOTE: This is currently only available in the Raspberry Pi version",
|
|
"This is a cut-down version of node.js's library",
|
|
"Please see http://nodemanual.org/latest/nodejs_ref_guide/http.html",
|
|
"NOTE: The HTTP client + server send in ~8 byte chunks. This is normally fine but big servers - eg. Google will reject requests made like this (DDoS protection?)"
|
|
]
|
|
}*/
|
|
/*JSON{ "type":"class",
|
|
"class" : "httpSrv",
|
|
"description" : ["The HTTP server created by http.createServer" ]
|
|
}*/
|
|
/*JSON{ "type":"class",
|
|
"class" : "httpSRq",
|
|
"description" : ["The HTTP server request" ]
|
|
}*/
|
|
/*JSON{ "type":"class",
|
|
"class" : "httpSRs",
|
|
"description" : ["The HTTP server response" ]
|
|
}*/
|
|
/*JSON{ "type":"class",
|
|
"class" : "httpCRq",
|
|
"description" : ["The HTTP client request" ]
|
|
}*/
|
|
/*JSON{ "type":"class",
|
|
"class" : "url",
|
|
"description" : ["This class helps to convert URLs into Objects of information ready for http.request/get" ]
|
|
}*/
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
/*JSON{ "type":"staticmethod",
|
|
"class" : "http", "name" : "createServer",
|
|
"generate" : "jswrap_http_createServer",
|
|
"description" : ["Create an HTTP Server" ],
|
|
"params" : [ [ "callback", "JsVarName", "A function(req,res) that will be called when a connection is made"] ],
|
|
"return" : ["JsVar", "Returns a new httpSrv object"]
|
|
}*/
|
|
|
|
JsVar *jswrap_http_createServer(JsVar *callback) {
|
|
JsVar *skippedCallback = jsvSkipName(callback);
|
|
if (!jsvIsFunction(skippedCallback)) {
|
|
jsError("Expecting Callback Function");
|
|
jsvUnLock(skippedCallback);
|
|
return 0;
|
|
}
|
|
jsvUnLock(skippedCallback);
|
|
return httpServerNew(callback);
|
|
}
|
|
|
|
/*JSON{ "type":"staticmethod",
|
|
"class" : "http", "name" : "request",
|
|
"generate" : "jswrap_http_request",
|
|
"description" : ["Create an HTTP Request - end() must be called on it to complete the operation" ],
|
|
"params" : [ [ "options", "JsVar", "An object containing host,port,path,method fields"],
|
|
[ "callback", "JsVarName", "A function(res) that will be called when a connection is made"] ],
|
|
"return" : ["JsVar", "Returns a new httpCRq object"]
|
|
}*/
|
|
|
|
JsVar *jswrap_http_request(JsVar *options, JsVar *callback) {
|
|
bool unlockOptions = false;
|
|
if (jsvIsString(options)) {
|
|
options = jswrap_url_parse(options);
|
|
unlockOptions = true;
|
|
}
|
|
if (!jsvIsObject(options)) {
|
|
jsError("Expecting Options to be an Object");
|
|
return 0;
|
|
}
|
|
JsVar *skippedCallback = jsvSkipName(callback);
|
|
if (!jsvIsFunction(skippedCallback)) {
|
|
jsError("Expecting Callback Function");
|
|
jsvUnLock(skippedCallback);
|
|
return 0;
|
|
}
|
|
jsvUnLock(skippedCallback);
|
|
JsVar *rq = httpClientRequestNew(options, callback);
|
|
if (unlockOptions) jsvUnLock(options);
|
|
return rq;
|
|
}
|
|
|
|
/*JSON{ "type":"staticmethod",
|
|
"class" : "http", "name" : "get",
|
|
"generate" : "jswrap_http_get",
|
|
"description" : ["Create an HTTP Request - convenience function for ```http.request()```. options.method is set to 'get', and end is called automatically" ],
|
|
"params" : [ [ "options", "JsVar", "An object containing host,port,path,method fields"],
|
|
[ "callback", "JsVarName", "A function(res) that will be called when a connection is made"] ],
|
|
"return" : ["JsVar", "Returns a new httpCRq object"]
|
|
}*/
|
|
JsVar *jswrap_http_get(JsVar *options, JsVar *callback) {
|
|
if (jsvIsObject(options)) {
|
|
// if options is a string - it will be parsed, and GET will be set automatically
|
|
JsVar *method = jsvNewFromString("GET");
|
|
jsvUnLock(jsvAddNamedChild(options, method, "method"));
|
|
jsvUnLock(method);
|
|
}
|
|
JsVar *skippedCallback = jsvSkipName(callback);
|
|
if (!jsvIsUndefined(skippedCallback) && !jsvIsFunction(skippedCallback)) {
|
|
jsError("Expecting Callback Function");
|
|
jsvUnLock(skippedCallback);
|
|
return 0;
|
|
}
|
|
jsvUnLock(skippedCallback);
|
|
JsVar *cliReq = jswrap_http_request(options, callback);
|
|
jswrap_httpCRq_end(cliReq, 0);
|
|
httpClientRequestEnd(cliReq);
|
|
return cliReq;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
/*JSON{ "type":"method",
|
|
"class" : "httpSrv", "name" : "listen",
|
|
"generate" : "jswrap_httpSrv_listen",
|
|
"params" : [ [ "port", "int", "The port to listen on"] ]
|
|
}*/
|
|
|
|
void jswrap_httpSrv_listen(JsVar *parent, int port) {
|
|
httpServerListen(parent, port);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
/*JSON{ "type":"method",
|
|
"class" : "httpSRs", "name" : "write",
|
|
"generate" : "jswrap_httpSRs_write",
|
|
"params" : [ [ "data", "JsVar", "A string containing data to send"] ]
|
|
}*/
|
|
void jswrap_httpSRs_write(JsVar *parent, JsVar *data) {
|
|
httpServerResponseData(parent, data);
|
|
}
|
|
|
|
/*JSON{ "type":"method",
|
|
"class" : "httpSRs", "name" : "end",
|
|
"generate" : "jswrap_httpSRs_end",
|
|
"params" : [ [ "data", "JsVar", "A string containing data to send"] ]
|
|
}*/
|
|
void jswrap_httpSRs_end(JsVar *parent, JsVar *data) {
|
|
if (!jsvIsUndefined(data)) jswrap_httpSRs_write(parent, data);
|
|
httpServerResponseEnd(parent);
|
|
}
|
|
|
|
|
|
/*JSON{ "type":"method",
|
|
"class" : "httpSRs", "name" : "writeHead",
|
|
"generate" : "jswrap_httpSRs_writeHead",
|
|
"params" : [ [ "statusCode", "int", "The HTTP status code"],
|
|
[ "headers", "JsVar", "An object containing the headers"] ]
|
|
}*/
|
|
void jswrap_httpSRs_writeHead(JsVar *parent, int statusCode, JsVar *headers) {
|
|
httpServerResponseWriteHead(parent, statusCode, headers);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
/*JSON{ "type":"method",
|
|
"class" : "httpCRq", "name" : "write",
|
|
"generate" : "jswrap_httpCRq_write",
|
|
"params" : [ [ "data", "JsVar", "A string containing data to send"] ]
|
|
}*/
|
|
void jswrap_httpCRq_write(JsVar *parent, JsVar *data) {
|
|
httpClientRequestWrite(parent, data);
|
|
}
|
|
|
|
/*JSON{ "type":"method",
|
|
"class" : "httpCRq", "name" : "end",
|
|
"description" : ["Finish this HTTP request - optional data to append as an argument" ],
|
|
"generate" : "jswrap_httpCRq_end",
|
|
"params" : [ [ "data", "JsVar", "A string containing data to send"] ]
|
|
}*/
|
|
void jswrap_httpCRq_end(JsVar *parent, JsVar *data) {
|
|
if (!jsvIsUndefined(data)) jswrap_httpCRq_write(parent, data);
|
|
httpClientRequestEnd(parent);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------------
|
|
/*JSON{ "type":"staticmethod",
|
|
"class" : "url", "name" : "parse",
|
|
"generate" : "jswrap_url_parse",
|
|
"params" : [ [ "urlStr", "JsVar", "A URL to be parsed"] ],
|
|
"return" : ["JsVar", "An object containing options for ```http.request``` or ```http.get```"]
|
|
}*/
|
|
JsVar *jswrap_url_parse(JsVar *url) {
|
|
if (!jsvIsString(url)) return 0;
|
|
JsVar *obj = jsvNewWithFlags(JSV_OBJECT);
|
|
if (!obj) return 0; // out of memory
|
|
|
|
// scan string to try and pick stuff out
|
|
JsvStringIterator it;
|
|
jsvStringIteratorNew(&it, url, 0);
|
|
int slashes = 0;
|
|
int colons = 0;
|
|
int addrStart = -1;
|
|
int portStart = -1;
|
|
int pathStart = -1;
|
|
int charIdx = 0;
|
|
int portNumber = 0;
|
|
while (jsvStringIteratorHasChar(&it)) {
|
|
char ch = jsvStringIteratorGetChar(&it);
|
|
if (ch == '/') {
|
|
slashes++;
|
|
if (addrStart>=0)
|
|
pathStart = charIdx;
|
|
if (colons==1 && slashes==2 && addrStart<0)
|
|
addrStart = charIdx;
|
|
}
|
|
if (ch == ':') {
|
|
colons++;
|
|
if (addrStart>=0 && pathStart<0)
|
|
portStart = charIdx;
|
|
}
|
|
|
|
if (portStart>=0 && charIdx>portStart && pathStart<0 && ch >= '0' && ch <= '9') {
|
|
portNumber = portNumber*10 + (ch-'0');
|
|
}
|
|
|
|
jsvStringIteratorNext(&it);
|
|
charIdx++;
|
|
}
|
|
jsvStringIteratorFree(&it);
|
|
// try and sort stuff out
|
|
if (pathStart<0) pathStart = charIdx;
|
|
if (pathStart<0) pathStart = charIdx;
|
|
int addrEnd = (portStart>=0) ? portStart : pathStart;
|
|
// pull out details
|
|
JsVar *method = jsvNewFromString("GET");
|
|
jsvUnLock(jsvAddNamedChild(obj, method, "method"));
|
|
jsvUnLock(method);
|
|
JsVar *host = jsvNewFromEmptyString();
|
|
jsvAppendStringVar(host, url, addrStart+1, addrEnd-(addrStart+1));
|
|
jsvUnLock(jsvAddNamedChild(obj, host, "host"));
|
|
jsvUnLock(host);
|
|
JsVar *path = jsvNewFromEmptyString();
|
|
jsvAppendStringVar(path, url, pathStart, JSVAPPENDSTRINGVAR_MAXLENGTH);
|
|
if (jsvGetStringLength(path)==0) jsvAppendString(path, "/");
|
|
jsvUnLock(jsvAddNamedChild(obj, path, "path"));
|
|
jsvUnLock(path);
|
|
if (portNumber<=0 || portNumber>65535) portNumber=80;
|
|
JsVar *port = jsvNewFromInteger(portNumber);
|
|
jsvUnLock(jsvAddNamedChild(obj, port, "port"));
|
|
jsvUnLock(port);
|
|
|
|
return obj;
|
|
}
|
|
|