-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
535 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
module web | ||
|
||
import vjs { Context, Value } | ||
import net.http { Method, Response, parse_multipart_form } | ||
|
||
fn fetch(this Value, args []Value) Value { | ||
mut error := this.ctx.js_undefined() | ||
promise := this.ctx.js_promise() | ||
url := args[0].str() | ||
opts := args[1] | ||
header := opts.get('headers') | ||
method := opts.get('method').str().to_lower() | ||
raw_body := opts.get('body') | ||
boundary := opts.get('boundary') | ||
mut hd := http.new_header() | ||
props := header.property_names() or { panic(err) } | ||
for data in props { | ||
key := data.atom.str() | ||
hd.set_custom(key, header.get(key).str()) or { | ||
error = this.ctx.js_error(message: err.msg()) | ||
unsafe { | ||
goto reject | ||
} | ||
break | ||
} | ||
} | ||
mut body := raw_body.str() | ||
mut resp := Response{} | ||
if boundary.is_undefined() { | ||
resp = http.fetch( | ||
url: url | ||
method: Method.from(method) or { Method.get } | ||
header: hd | ||
data: body | ||
) or { | ||
error = this.ctx.js_error(message: err.msg()) | ||
unsafe { | ||
goto reject | ||
} | ||
Response{} | ||
} | ||
} else { | ||
form, files := parse_multipart_form(body, '----formdata-' + boundary.str()) | ||
resp = http.post_multipart_form(url, form: form, header: hd, files: files) or { | ||
error = this.ctx.js_error(message: err.msg()) | ||
unsafe { | ||
goto reject | ||
} | ||
Response{} | ||
} | ||
} | ||
mut resp_header := resp.header | ||
obj_header := this.ctx.js_object() | ||
for key in resp_header.keys() { | ||
val := resp_header.custom_values(key) | ||
obj_header.set(key, val.join('; ')) | ||
} | ||
obj := this.ctx.js_object() | ||
obj.set('body', resp.body) | ||
obj.set('status', resp.status_code) | ||
obj.set('status_message', resp.status_msg) | ||
obj.set('header', obj_header) | ||
resp_header.free() | ||
return promise.resolve(obj) | ||
reject: | ||
return promise.reject(error) | ||
} | ||
|
||
fn fetch_boot(ctx &Context, boot Value) { | ||
boot.set('core_fetch', ctx.js_function_this(fetch)) | ||
} | ||
|
||
// Add Fetch API to globals. | ||
// Example: | ||
// ```v | ||
// import herudi.vjs | ||
// import herudi.vjs.web | ||
// | ||
// fn main() { | ||
// rt := vjs.new_runtime() | ||
// ctx := rt.new_context() | ||
// | ||
// web.fetch_api(ctx) | ||
// } | ||
// ``` | ||
pub fn fetch_api(ctx &Context) { | ||
glob, boot := get_bootstrap(ctx) | ||
fetch_boot(ctx, boot) | ||
ctx.eval_file('${@VMODROOT}/web/js/fetch.js', vjs.type_module) or { panic(err) } | ||
glob.free() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Request } from "./fetch/request.js"; | ||
import { Response } from "./fetch/response.js"; | ||
import { Headers } from "./fetch/headers.js"; | ||
|
||
const { core_fetch } = globalThis.__bootstrap; | ||
|
||
async function fetch(input, opts = {}) { | ||
const req = new Request(input, opts); | ||
const url = req.url; | ||
const boundary = opts.body instanceof FormData | ||
? Math.random().toString() | ||
: void 0; | ||
const body = typeof opts.body !== "string" | ||
? await req.text(boundary) | ||
: opts.body; | ||
const res = await core_fetch(url, { | ||
method: opts.method ?? "GET", | ||
headers: req.headers.toJSON(), | ||
body: body, | ||
boundary: boundary, | ||
}); | ||
const resInit = {}; | ||
resInit.status = res.status; | ||
resInit.statusText = res.status_message; | ||
resInit.headers = res.header; | ||
resInit.url = url; | ||
return new Response(res.body, resInit); | ||
} | ||
|
||
globalThis.Headers = Headers; | ||
globalThis.Request = Request; | ||
globalThis.Response = Response; | ||
globalThis.fetch = fetch; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
const { isTypedArray, isArrayBuffer } = globalThis.__bootstrap.util; | ||
const encoder = new TextEncoder(); | ||
const decoder = new TextDecoder(); | ||
function streamToStr(stream) { | ||
const reader = stream.getReader(); | ||
let result = ""; | ||
async function read() { | ||
const { done, value } = await reader.read(); | ||
if (done) { | ||
return result; | ||
} | ||
result += decoder.decode(value, { stream: true }); | ||
return read(); | ||
} | ||
return read(); | ||
} | ||
const toStream = (data) => | ||
new ReadableStream({ | ||
start(ctrl) { | ||
ctrl.enqueue(data); | ||
ctrl.close(); | ||
}, | ||
}); | ||
export class Body { | ||
#body = {}; | ||
#bodyUsed = false; | ||
constructor(body) { | ||
if (body != null) { | ||
if (typeof body === "string") { | ||
this.#body.text = body; | ||
} else if (body instanceof URLSearchParams) { | ||
this.#body.text = body.toString(); | ||
} else if (body instanceof FormData) { | ||
this.#body.form = body; | ||
this.#body.blob = body["_blob"]; | ||
} else if (body instanceof ReadableStream) { | ||
this.#body.stream = body; | ||
} else if (body instanceof Blob) { | ||
this.#body.blob = body; | ||
} else if (isArrayBuffer(body) || isTypedArray(body)) { | ||
body = isArrayBuffer(body) ? body : body.buffer; | ||
this.#body.arrayBuffer = body; | ||
this.#body.text = decoder.decode(body); | ||
} | ||
} | ||
} | ||
#checkUpdateBody() { | ||
if (this.#bodyUsed) { | ||
throw new TypeError("Body already consumed."); | ||
} | ||
this.#bodyUsed = true; | ||
} | ||
#realBlob(code) { | ||
return typeof this.#body.blob === "function" | ||
? this.#body.blob(code) | ||
: this.#body.blob; | ||
} | ||
get bodyUsed() { | ||
return this.#bodyUsed; | ||
} | ||
get body() { | ||
if (this.#body.stream !== void 0) { | ||
return this.#body.stream; | ||
} | ||
if (this.#body.text !== void 0) { | ||
return toStream(encoder.encode(this.#body.text)); | ||
} | ||
if (this.#body.blob !== void 0) { | ||
return this.#realBlob().stream(); | ||
} | ||
if (this.#body.arrayBuffer !== void 0) { | ||
return toStream(new Uint8Array(this.#body.arrayBuffer)); | ||
} | ||
return null; | ||
} | ||
|
||
text(code) { | ||
this.#checkUpdateBody(); | ||
if (this.#body.text !== void 0) { | ||
return Promise.resolve(this.#body.text); | ||
} | ||
if (this.#body.arrayBuffer !== void 0) { | ||
return Promise.resolve(decoder.decode(this.#body.arrayBuffer)); | ||
} | ||
if (this.#body.blob !== void 0) { | ||
const blob = this.#realBlob(code); | ||
return blob.text(); | ||
} | ||
if (this.#body.stream !== void 0) { | ||
return streamToStr(this.#body.stream); | ||
} | ||
return Promise.resolve(""); | ||
} | ||
|
||
json() { | ||
return this.text().then(JSON.parse); | ||
} | ||
|
||
formData() { | ||
this.#checkUpdateBody(); | ||
if (this.#body.form !== void 0) { | ||
return Promise.resolve(this.#body.form); | ||
} | ||
throw new TypeError("Can't read form-data from body"); | ||
} | ||
|
||
blob() { | ||
this.#checkUpdateBody(); | ||
if (this.#body.blob !== void 0) { | ||
return Promise.resolve(this.#realBlob()); | ||
} | ||
throw new TypeError("Can't read blob from body"); | ||
} | ||
|
||
arrayBuffer() { | ||
if (this.#body.arrayBuffer !== void 0) { | ||
this.#checkUpdateBody(); | ||
return Promise.resolve(this.#body.arrayBuffer); | ||
} | ||
return this.text().then(encoder.encode); | ||
} | ||
} |
Oops, something went wrong.