Initial commit
This commit is contained in:
commit
8fe49c3593
|
@ -0,0 +1,2 @@
|
|||
tests/index.html -linguist-detectable
|
||||
tests/index.html linguist-vendored
|
|
@ -0,0 +1,9 @@
|
|||
.idea
|
||||
tests/client/main.wasm
|
||||
tests/server/server
|
||||
tests/server/server.crt
|
||||
tests/server/server.key
|
||||
tests/server/server.csr
|
||||
tests/server/ca.crt
|
||||
tests/server/ca.key
|
||||
tests/server/ca.srl
|
|
@ -0,0 +1,157 @@
|
|||
# GNU LESSER GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates the
|
||||
terms and conditions of version 3 of the GNU General Public License,
|
||||
supplemented by the additional permissions listed below.
|
||||
|
||||
## 0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the
|
||||
GNU General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License, other
|
||||
than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
## 1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
## 2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
- a) under this License, provided that you make a good faith effort
|
||||
to ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
- b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
## 3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from a
|
||||
header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
- a) Give prominent notice with each copy of the object code that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
- b) Accompany the object code with a copy of the GNU GPL and this
|
||||
license document.
|
||||
|
||||
## 4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that, taken
|
||||
together, effectively do not restrict modification of the portions of
|
||||
the Library contained in the Combined Work and reverse engineering for
|
||||
debugging such modifications, if you also do each of the following:
|
||||
|
||||
- a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
- b) Accompany the Combined Work with a copy of the GNU GPL and this
|
||||
license document.
|
||||
- c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
- d) Do one of the following:
|
||||
- 0) Convey the Minimal Corresponding Source under the terms of
|
||||
this License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
- 1) Use a suitable shared library mechanism for linking with
|
||||
the Library. A suitable mechanism is one that (a) uses at run
|
||||
time a copy of the Library already present on the user's
|
||||
computer system, and (b) will operate properly with a modified
|
||||
version of the Library that is interface-compatible with the
|
||||
Linked Version.
|
||||
- e) Provide Installation Information, but only if you would
|
||||
otherwise be required to provide such information under section 6
|
||||
of the GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the Application
|
||||
with a modified version of the Linked Version. (If you use option
|
||||
4d0, the Installation Information must accompany the Minimal
|
||||
Corresponding Source and Corresponding Application Code. If you
|
||||
use option 4d1, you must provide the Installation Information in
|
||||
the manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.)
|
||||
|
||||
## 5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the Library
|
||||
side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
- a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities, conveyed under the terms of this License.
|
||||
- b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
## 6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
as you received it specifies that a certain numbered version of the
|
||||
GNU Lesser General Public License "or any later version" applies to
|
||||
it, you have the option of following the terms and conditions either
|
||||
of that published version or of any later version published by the
|
||||
Free Software Foundation. If the Library as you received it does not
|
||||
specify a version number of the GNU Lesser General Public License, you
|
||||
may choose any version of the GNU Lesser General Public License ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
|
@ -0,0 +1,8 @@
|
|||
# jsFetch
|
||||
|
||||
Go library to bridge net/http and the JS Fetch API, without actually importing net/http.
|
||||
Made using the [jsStreams](https://git.ailur.dev/Ailur/jsStreams) library.
|
||||
|
||||
[![Go Report Card](https://goreportcard.com/badge/git.ailur.dev/ailur/jsFetch)](https://goreportcard.com/report/git.ailur.dev/ailur/jsFetch) [![Go Reference](https://pkg.go.dev/badge/git.ailur.dev/ailur/jsFetch.svg)](https://pkg.go.dev/git.ailur.dev/ailur/jsFetch)
|
||||
|
||||
The API is exactly the same as net/http.
|
|
@ -0,0 +1,8 @@
|
|||
module git.ailur.dev/ailur/jsFetch
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
git.ailur.dev/ailur/jsStreams v1.2.0
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
git.ailur.dev/ailur/jsStreams v1.2.0 h1:BRtLEyjkUoPKPu0Y6odUbSMlKCYNyR792TYRtujKfPw=
|
||||
git.ailur.dev/ailur/jsStreams v1.2.0/go.mod h1:/ZCvbUcWkZRuKIkO7jH6b5vIjzdxIOP8ET8X0src5Go=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
|
@ -0,0 +1,696 @@
|
|||
package jsFetch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"maps"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"crypto/tls"
|
||||
"mime/multipart"
|
||||
"net/url"
|
||||
"syscall/js"
|
||||
|
||||
"git.ailur.dev/ailur/jsStreams"
|
||||
)
|
||||
|
||||
// Transport is here only for compatibility with the Go standard library.
|
||||
// All fields are ignored in fetch.
|
||||
type Transport struct {
|
||||
// Proxy specifies a function to return a proxy for a given Request.
|
||||
// It is ignored in fetch.
|
||||
Proxy func(*Request) (*url.URL, error)
|
||||
|
||||
// OnProxyError specifies a function to handle errors that occur while fetching a proxy.
|
||||
// It is ignored in fetch.
|
||||
OnProxyError func(*Request, *url.URL, error)
|
||||
|
||||
// DialContext specifies the dial function for creating unencrypted TCP connections.
|
||||
// It is ignored in fetch.
|
||||
DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Dial specifies the dial function for creating unencrypted TCP connections.
|
||||
// It is ignored in fetch.
|
||||
Dial func(network, addr string) (net.Conn, error)
|
||||
|
||||
// DialTLSContext specifies the dial function for creating TLS connections.
|
||||
// It is ignored in fetch.
|
||||
DialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// DialTLS specifies the dial function for creating TLS connections.
|
||||
// It is ignored in fetch.
|
||||
DialTLS func(network, addr string) (net.Conn, error)
|
||||
|
||||
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
||||
// It is ignored in fetch.
|
||||
TLSClientConfig *tls.Config
|
||||
|
||||
// TLSHandshakeTimeout specifies the maximum amount of time waiting to wait for a TLS handshake.
|
||||
// It is ignored in fetch.
|
||||
TLSHandshakeTimeout time.Duration
|
||||
|
||||
// DisableKeepAlives specifies whether to disable keep-alive connections.
|
||||
// It is ignored in fetch.
|
||||
DisableKeepAlives bool
|
||||
|
||||
// DisableCompression specifies whether to disable compression.
|
||||
// It is ignored in fetch.
|
||||
DisableCompression bool
|
||||
|
||||
// MaxIdleConns specifies the maximum number of idle (keep-alive) connections to keep.
|
||||
// It is ignored in fetch.
|
||||
MaxIdleConns int
|
||||
|
||||
// MaxIdleConnsPerHost specifies the maximum number of idle (keep-alive) connections to keep per host.
|
||||
// It is ignored in fetch.
|
||||
MaxIdleConnsPerHost int
|
||||
|
||||
// IdleConnTimeout specifies the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||
// It is ignored in fetch.
|
||||
IdleConnTimeout time.Duration
|
||||
|
||||
// ResponseHeaderTimeout specifies the maximum amount of time to wait for a response header.
|
||||
// It is ignored in fetch.
|
||||
ResponseHeaderTimeout time.Duration
|
||||
|
||||
// ExpectContinueTimeout specifies the maximum amount of time to wait for a 100-continue response.
|
||||
// It is ignored in fetch.
|
||||
ExpectContinueTimeout time.Duration
|
||||
|
||||
// TLSNextProto specifies a function to upgrade the connection to a different protocol.
|
||||
// It is ignored in fetch.
|
||||
TLSNextProto map[string]func(authority string, c net.Conn) RoundTripper
|
||||
|
||||
// ProxyConnectHeader specifies the headers to send to proxies during CONNECT requests.
|
||||
// It is ignored in fetch.
|
||||
ProxyConnectHeader Header
|
||||
|
||||
// MaxResponseHeaderBytes specifies the maximum number of bytes to read from the server's response headers.
|
||||
// It is ignored in fetch.
|
||||
MaxResponseHeaderBytes int
|
||||
|
||||
// WriteBufferSize specifies the size of the write buffer.
|
||||
// It is ignored in fetch.
|
||||
WriteBufferSize int
|
||||
|
||||
// ReadBufferSize specifies the size of the read buffer.
|
||||
// It is ignored in fetch.
|
||||
ReadBufferSize int
|
||||
|
||||
// ForceAttemptHTTP2 specifies whether to force an attempt to use HTTP/2.
|
||||
// It is ignored in fetch.
|
||||
ForceAttemptHTTP2 bool
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP transaction, returning a Response for the provided Request.
|
||||
// In the context of fetch, it will automatically follow redirects.
|
||||
// This implementation is a wrapper around the fetch API.
|
||||
func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) {
|
||||
defer func() {
|
||||
recovery := recover()
|
||||
if recovery != nil {
|
||||
runtimeErr, ok := recovery.(runtime.Error)
|
||||
if ok {
|
||||
err = runtimeErr
|
||||
} else {
|
||||
err = errors.New(recovery.(string))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if req.GetBody != nil {
|
||||
req.Body, err = req.GetBody()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
headersMapStringInterface := make(map[string]interface{})
|
||||
for key, value := range req.Header {
|
||||
headersMapStringInterface[key] = value
|
||||
}
|
||||
if req.Body != nil {
|
||||
headersMapStringInterface["Content-Length"] = req.ContentLength
|
||||
}
|
||||
fetchArgs := map[string]interface{}{
|
||||
"method": req.Method,
|
||||
"headers": headersMapStringInterface,
|
||||
}
|
||||
// Since all supported browsers are chromium based, lets just detect for chrome.
|
||||
// If we are, we can use a streamed client body
|
||||
if req.Method != "GET" && req.Method != "HEAD" && req.Body != nil {
|
||||
if !req.DisableStreamedClient {
|
||||
if !js.Global().Get("chrome").IsUndefined() {
|
||||
req.DisableStreamedClientChecks = true
|
||||
}
|
||||
}
|
||||
var jsBody js.Value
|
||||
if req.DisableStreamedClientChecks && !req.DisableStreamedClient {
|
||||
jsBody = jsStreams.ReaderToReadableStream(req.Body)
|
||||
fetchArgs["duplex"] = "half"
|
||||
} else {
|
||||
// Not today firefox, not today
|
||||
// Mozilla, please add support for streamed client bodies
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jsBody = js.Global().Get("Uint8Array").New(len(body))
|
||||
js.CopyBytesToJS(jsBody, body)
|
||||
}
|
||||
fetchArgs["body"] = jsBody
|
||||
}
|
||||
promise := js.Global().Call("fetch", req.URL.String(), js.ValueOf(fetchArgs))
|
||||
|
||||
var waitGroup sync.WaitGroup
|
||||
waitGroup.Add(1)
|
||||
|
||||
promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
resp = new(Response)
|
||||
resp.Request = req
|
||||
resp.Header = make(Header)
|
||||
args[0].Get("headers").Call("forEach", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
resp.Header.Set(args[0].String(), args[1].String())
|
||||
return nil
|
||||
}))
|
||||
resp.Status = args[0].Get("statusText").String()
|
||||
resp.StatusCode = args[0].Get("status").Int()
|
||||
if resp.Header.Has("Content-Length") {
|
||||
resp.ContentLength, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
resp.ContentLength = -1
|
||||
}
|
||||
}
|
||||
resp.Body = jsStreams.NewReadableStream(args[0].Get("body"))
|
||||
|
||||
// Standard-library compatibility fields
|
||||
resp.Proto = "HTTP/1.1"
|
||||
resp.ProtoMajor = 1
|
||||
resp.ProtoMinor = 1
|
||||
resp.TransferEncoding = []string{"chunked"}
|
||||
resp.Trailer = make(Header)
|
||||
resp.TLS = nil
|
||||
resp.Close = false
|
||||
resp.Uncompressed = true
|
||||
|
||||
waitGroup.Done()
|
||||
return nil
|
||||
}))
|
||||
|
||||
promise.Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
err = errors.New(args[0].Get("message").String())
|
||||
waitGroup.Done()
|
||||
return nil
|
||||
}))
|
||||
|
||||
waitGroup.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
// Clone returns a copy of the Transport.
|
||||
func (t *Transport) Clone() (newTransport *Transport) {
|
||||
newTransport = new(Transport)
|
||||
newTransport.Proxy = t.Proxy
|
||||
newTransport.OnProxyError = t.OnProxyError
|
||||
newTransport.DialContext = t.DialContext
|
||||
newTransport.Dial = t.Dial
|
||||
newTransport.DialTLSContext = t.DialTLSContext
|
||||
newTransport.DialTLS = t.DialTLS
|
||||
newTransport.TLSClientConfig = t.TLSClientConfig
|
||||
newTransport.TLSHandshakeTimeout = t.TLSHandshakeTimeout
|
||||
newTransport.DisableKeepAlives = t.DisableKeepAlives
|
||||
newTransport.DisableCompression = t.DisableCompression
|
||||
newTransport.MaxIdleConns = t.MaxIdleConns
|
||||
newTransport.MaxIdleConnsPerHost = t.MaxIdleConnsPerHost
|
||||
newTransport.IdleConnTimeout = t.IdleConnTimeout
|
||||
newTransport.ResponseHeaderTimeout = t.ResponseHeaderTimeout
|
||||
newTransport.ExpectContinueTimeout = t.ExpectContinueTimeout
|
||||
newTransport.TLSNextProto = t.TLSNextProto
|
||||
newTransport.ProxyConnectHeader = t.ProxyConnectHeader.Clone()
|
||||
newTransport.MaxResponseHeaderBytes = t.MaxResponseHeaderBytes
|
||||
newTransport.WriteBufferSize = t.WriteBufferSize
|
||||
newTransport.ReadBufferSize = t.ReadBufferSize
|
||||
newTransport.ForceAttemptHTTP2 = t.ForceAttemptHTTP2
|
||||
return
|
||||
}
|
||||
|
||||
// CloseIdleConnections closes any idle connections.
|
||||
// This does nothing in fetch.
|
||||
func (t *Transport) CloseIdleConnections() {}
|
||||
|
||||
// RegisterProtocol registers a new protocol with a custom RoundTripper.
|
||||
// This does nothing in fetch.
|
||||
func (t *Transport) RegisterProtocol(protocol string, rt RoundTripper) {}
|
||||
|
||||
// FetchRoundTripper is a wrapper around the fetch API. It is used to make requests.
|
||||
// It is the default RoundTripper used for this subset of net/http.
|
||||
var FetchRoundTripper RoundTripper = &Transport{}
|
||||
|
||||
// RoundTripper is an interface representing the ability to execute a single HTTP transaction.
|
||||
type RoundTripper interface {
|
||||
// RoundTrip executes a single HTTP transaction, returning a Response for the provided Request.
|
||||
// In the context of fetch, it will automatically follow redirects.
|
||||
RoundTrip(*Request) (*Response, error)
|
||||
}
|
||||
|
||||
// Client is a fetch client. It is used to make requests.
|
||||
type Client struct {
|
||||
// Transport specifies the mechanism by which individual requests are made.
|
||||
// If nil, FetchRoundTripper is used.
|
||||
Transport RoundTripper
|
||||
|
||||
// Jar does nothing, but it is required for compatibility with the Go standard library.
|
||||
Jar CookieJar
|
||||
|
||||
// Timeout specifies a time limit for requests made by this Client.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Do sends an HTTP request and returns an HTTP response, following policy (such as redirects, cookies, auth) as configured on the client.
|
||||
func (c *Client) Do(req *Request) (resp *Response, err error) {
|
||||
if c.Transport == nil {
|
||||
c.Transport = FetchRoundTripper
|
||||
}
|
||||
return c.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
// CookieJar does nothing, but it is required for compatibility with the Go standard library.
|
||||
type CookieJar interface {
|
||||
SetCookies(u *url.URL, cookies []*Cookie)
|
||||
Cookies(u *url.URL) []*Cookie
|
||||
}
|
||||
|
||||
// SameSite does nothing, but it is required for compatibility with the Go standard library.
|
||||
type SameSite int
|
||||
|
||||
// Cookie does nothing, but it is required for compatibility with the Go standard library.
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
Quoted bool
|
||||
Path string
|
||||
Domain string
|
||||
Expires time.Time
|
||||
RawExpires string
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
SameSite SameSite
|
||||
Partitioned bool
|
||||
Raw string
|
||||
Unparsed []string
|
||||
}
|
||||
|
||||
// Fetch is a fetch client. It is used to make requests.
|
||||
// It wraps around JS fetch.
|
||||
var Fetch = &Client{
|
||||
Transport: FetchRoundTripper,
|
||||
Timeout: 20 * time.Second,
|
||||
}
|
||||
|
||||
func CanonicalHeaderKey(key string) string {
|
||||
const allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~"
|
||||
for _, char := range []rune(key) {
|
||||
if !strings.Contains(allowedCharacters, string(char)) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
splitStrings := strings.Split(key, "-")
|
||||
for splitString, _ := range splitStrings {
|
||||
var stringBuilder strings.Builder
|
||||
stringBuilder.WriteRune(unicode.ToUpper([]rune(splitStrings[splitString])[0]))
|
||||
stringBuilder.WriteString(strings.ToLower(splitStrings[splitString][1:]))
|
||||
splitStrings[splitString] = stringBuilder.String()
|
||||
}
|
||||
return strings.Join(splitStrings, "-")
|
||||
}
|
||||
|
||||
type Header map[string]string
|
||||
|
||||
func (h Header) Add(key, value string) {
|
||||
h[CanonicalHeaderKey(key)] = value
|
||||
}
|
||||
|
||||
func (h Header) Clone() (newHeader Header) {
|
||||
maps.Copy(h, newHeader)
|
||||
return
|
||||
}
|
||||
|
||||
func (h Header) Del(key string) {
|
||||
delete(h, CanonicalHeaderKey(key))
|
||||
}
|
||||
|
||||
func (h Header) Get(key string) (value string) {
|
||||
value, _ = h[CanonicalHeaderKey(key)]
|
||||
return
|
||||
}
|
||||
|
||||
func (h Header) Set(key, value string) {
|
||||
h[CanonicalHeaderKey(key)] = value
|
||||
}
|
||||
|
||||
func (h Header) Has(key string) (has bool) {
|
||||
_, has = h[CanonicalHeaderKey(key)]
|
||||
return
|
||||
}
|
||||
|
||||
func (h Header) Values() (values []string) {
|
||||
for _, value := range h {
|
||||
values = append(values, value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h Header) Write(w io.Writer) error {
|
||||
for key, value := range h {
|
||||
_, err := w.Write([]byte(key + ": " + value + "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
|
||||
for key, value := range h {
|
||||
excludeKey, _ := exclude[key]
|
||||
if excludeKey {
|
||||
continue
|
||||
}
|
||||
_, err := w.Write([]byte(key + ": " + value + "\r\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
// DisableStreamedClient specifies whether to use a streamed client body.
|
||||
// This is set to false by default, but has checks to make sure it's running on a supported browser.
|
||||
// HTTP/2 or QUIC to be enabled when using V8, which is not always the case, particularly on
|
||||
// older servers or test servers.
|
||||
//
|
||||
// If DisableStreamedClientChecks is set to true, the client will first attempt to detect if
|
||||
// the server supports HTTP/2 or QUIC and if you are running a supported JavaScript engine.
|
||||
// Supported browser engines include:
|
||||
// - V8 (Chrome, Edge, Opera)
|
||||
// Unsupported browser engines include:
|
||||
// - SpiderMonkey (Firefox)
|
||||
// - JavaScriptCore (Safari)
|
||||
// - Chakra (Internet Explorer)
|
||||
// - KJS (Konqueror)
|
||||
// - Presto (Opera Mini and ancient versions of Opera)
|
||||
// Data from https://caniuse.com/mdn-api_request_request_request_body_readablestream
|
||||
// TL;DR If it's chromium it'll work.
|
||||
DisableStreamedClient bool
|
||||
|
||||
// DisableStreamedClientChecks specifies whether to disable checks for streamed clients.
|
||||
// It does nothing if UseStreamedClient is set to false.
|
||||
// Having it set to false may add another HEAD request to the server, which may be undesirable.
|
||||
// Also, forcing it on may be useful if SpiderMonkey one day supports streamed clients bodies.
|
||||
DisableStreamedClientChecks bool
|
||||
|
||||
// Method specifies the HTTP method (GET, POST, PUT, etc.).
|
||||
Method string
|
||||
|
||||
// URL specifies either the URL to fetch as a string, or a URL object.
|
||||
URL *url.URL
|
||||
|
||||
// Headers is a Headers object, allowing you to set request headers.
|
||||
Header Header
|
||||
|
||||
// Body is an optional body to be sent with the request.
|
||||
Body io.ReadCloser
|
||||
|
||||
// GetBody is an optional function that returns a ReadCloser for the body.
|
||||
GetBody func() (io.ReadCloser, error)
|
||||
|
||||
// ContentLength is the length of the body. It is mostly superseded by the Content-Length header.
|
||||
ContentLength int64
|
||||
|
||||
// The following fields are not used by fetch and exist mostly for compatibility with the Go standard library.
|
||||
// They will do nothing. Do not use them.
|
||||
|
||||
// Proto, ProtoMajor, ProtoMinor specify the HTTP protocol version.
|
||||
// This is useless, as fetch does not allow you to specify the protocol version.
|
||||
Proto string
|
||||
ProtoMajor int
|
||||
ProtoMinor int
|
||||
|
||||
// Close indicates whether to close the connection after the request.
|
||||
// This is useless, as fetch does not allow you to specify whether to close the connection and instead forces you to use a WebSocket.
|
||||
Close bool
|
||||
|
||||
// Host specifies the host to perform the request to.
|
||||
// This is useless, as it only makes sense in the context of the Host or :authority headers, both of which are filled-in automatically by fetch.
|
||||
Host string
|
||||
|
||||
// TransferEncoding specifies the transfer encodings to be used.
|
||||
// This is useless, as since we always use a stream, the transfer encoding is always chunked.
|
||||
TransferEncoding []string
|
||||
|
||||
// Form, PostForm, and MultipartForm specify the parsed form data.
|
||||
// This is useless, because even the Go standard library does not use it.
|
||||
// Use Body instead.
|
||||
Form url.Values
|
||||
PostForm url.Values
|
||||
MultipartForm *url.Values
|
||||
|
||||
// Trailer specifies additional headers that are sent after the request body.
|
||||
// This is useless, as fetch does not allow you to specify trailers.
|
||||
// Not to mention, nothing supports trailers.
|
||||
Trailer Header
|
||||
|
||||
// RemoteAddr and RequestURI specify the remote address and Request-URI for the request.
|
||||
// This is useless, as fetch does not allow you to specify the remote address, and you should use URL instead of RequestURI.
|
||||
RemoteAddr string
|
||||
RequestURI string
|
||||
|
||||
// TLS allows you to specify the TLS connection state.
|
||||
// This is useless, as fetch does not allow you to specify the TLS connection state.
|
||||
TLS *tls.ConnectionState
|
||||
|
||||
// Cancel is an optional channel that can be used to cancel the request.
|
||||
// This isn't even in the Go standard library anymore.
|
||||
Cancel <-chan struct{}
|
||||
|
||||
// Response is the response that caused this request to be created, usually in a redirect.
|
||||
// This is useless, as fetch follows redirects automatically.
|
||||
Response *Response
|
||||
|
||||
// Pattern is the pattern that was matched for this request.
|
||||
// This is useless, as fetch does not support pattern matching.
|
||||
Pattern string
|
||||
}
|
||||
|
||||
// AddCookie does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) AddCookie(c *Cookie) {}
|
||||
|
||||
// BasicAuth does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) BasicAuth() (username, password string, ok bool) { return }
|
||||
|
||||
func (r *Request) Clone() (newRequest *Request) {
|
||||
newRequest = new(Request)
|
||||
newRequest.Method = r.Method
|
||||
newRequest.URL = r.URL
|
||||
newRequest.Header = r.Header.Clone()
|
||||
newRequest.Body = r.Body
|
||||
newRequest.GetBody = r.GetBody
|
||||
newRequest.ContentLength = r.ContentLength
|
||||
newRequest.TransferEncoding = r.TransferEncoding
|
||||
newRequest.Proto = r.Proto
|
||||
newRequest.ProtoMajor = r.ProtoMajor
|
||||
newRequest.ProtoMinor = r.ProtoMinor
|
||||
newRequest.Close = r.Close
|
||||
newRequest.Host = r.Host
|
||||
newRequest.Form = r.Form
|
||||
newRequest.PostForm = r.PostForm
|
||||
newRequest.MultipartForm = r.MultipartForm
|
||||
newRequest.Trailer = r.Trailer.Clone()
|
||||
newRequest.RemoteAddr = r.RemoteAddr
|
||||
newRequest.RequestURI = r.RequestURI
|
||||
newRequest.TLS = r.TLS
|
||||
newRequest.Cancel = r.Cancel
|
||||
newRequest.Response = r.Response
|
||||
newRequest.Pattern = r.Pattern
|
||||
return
|
||||
}
|
||||
|
||||
// Context does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) Context() context.Context { return context.Background() }
|
||||
|
||||
// Cookie does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) Cookie(name string) (*Cookie, error) { return nil, nil }
|
||||
|
||||
// Cookies does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) Cookies() []*Cookie { return nil }
|
||||
|
||||
// CookiesNamed does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) CookiesNamed(name string) []*Cookie { return nil }
|
||||
|
||||
// FormFile does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// FormValue does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) FormValue(key string) string { return "" }
|
||||
|
||||
// MultipartReader does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) MultipartReader() (*multipart.Reader, error) { return nil, nil }
|
||||
|
||||
// ParseForm does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) ParseForm() error { return nil }
|
||||
|
||||
// ParseMultipartForm does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) ParseMultipartForm(maxMemory int64) error { return nil }
|
||||
|
||||
// PostFormValue does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) PostFormValue(key string) string { return "" }
|
||||
|
||||
// ProtoAtLeast does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) ProtoAtLeast(major, minor int) bool { return false }
|
||||
|
||||
// Finally, something that does something!
|
||||
|
||||
// Referer returns the value of the Referer header.
|
||||
func (r *Request) Referer() string {
|
||||
return r.Header.Get("Referer")
|
||||
}
|
||||
|
||||
// SetBasicAuth does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) SetBasicAuth(username, password string) {}
|
||||
|
||||
// UserAgent returns the value of the User-Agent header.
|
||||
func (r *Request) UserAgent() string {
|
||||
return r.Header.Get("User-Agent")
|
||||
}
|
||||
|
||||
// WithContext does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) WithContext(ctx context.Context) *Request { return r }
|
||||
|
||||
// Write writes an HTTP/1.1 request, which is the header and body, in wire format.
|
||||
// It is not yet implemented (and probably never will be, because after all, why would you need to write an HTTP request in wire format?).
|
||||
func (r *Request) Write(w io.Writer) error { return errors.New("not implemented") }
|
||||
|
||||
// WriteProxy does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Request) WriteProxy(w io.Writer) error { return nil }
|
||||
|
||||
func NewRequest(method, uri string, body io.Reader) (request *Request, err error) {
|
||||
request = new(Request)
|
||||
request.Method = method
|
||||
request.URL, err = url.Parse(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request.Header = make(Header)
|
||||
request.Body = io.NopCloser(body)
|
||||
return
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// Status specifies the HTTP status.
|
||||
// It will be an empty string if using HTTP/2.
|
||||
Status string
|
||||
|
||||
// StatusCode specifies the HTTP status code.
|
||||
StatusCode int
|
||||
|
||||
// Header specifies the response headers.
|
||||
Header Header
|
||||
|
||||
// Body is the response body.
|
||||
Body io.ReadCloser
|
||||
|
||||
// TransferEncoding specifies the transfer encodings that have been applied to the response.
|
||||
TransferEncoding []string
|
||||
|
||||
// ContentLength specifies the length of the body.
|
||||
// It is mostly superseded by the Content-Length header.
|
||||
// A value of -1 indicates that the length is unknown.
|
||||
ContentLength int64
|
||||
|
||||
// Request is the request that was made to get this response.
|
||||
Request *Request
|
||||
|
||||
// The following fields are not used by fetch and exist mostly for compatibility with the Go standard library.
|
||||
// They will do nothing. Do not use them.
|
||||
|
||||
// Proto, ProtoMajor, ProtoMinor specify the HTTP protocol version.
|
||||
// This is useless, as fetch does not allow you to specify the protocol version.
|
||||
Proto string
|
||||
ProtoMajor int
|
||||
ProtoMinor int
|
||||
|
||||
// Close indicates whether to close the connection after the request.
|
||||
// This is useless, as fetch does not allow you to specify whether to close the connection and instead forces you to use a WebSocket.
|
||||
Close bool
|
||||
|
||||
// Uncompressed specifies whether the response is uncompressed.
|
||||
// This is useless, as fetch does not allow you to specify whether the response is uncompressed.
|
||||
Uncompressed bool
|
||||
|
||||
// Trailer specifies additional headers that are sent after the request body.
|
||||
// This is useless, as fetch does not allow you to specify trailers.
|
||||
// Not to mention, nothing supports trailers.
|
||||
Trailer Header
|
||||
|
||||
// TLS allows you to specify the TLS connection state.
|
||||
// This is useless, as fetch does not allow you to specify the TLS connection state.
|
||||
TLS *tls.ConnectionState
|
||||
}
|
||||
|
||||
func Get(url string) (response *Response, err error) {
|
||||
request, err := NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
response, err = Fetch.Do(request)
|
||||
return
|
||||
}
|
||||
|
||||
func Post(url string, body io.Reader) (response *Response, err error) {
|
||||
request, err := NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
response, err = Fetch.Do(request)
|
||||
return
|
||||
}
|
||||
|
||||
func PostForm(url string, data url.Values) (response *Response, err error) {
|
||||
body := io.NopCloser(strings.NewReader(data.Encode()))
|
||||
request, err := NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
response, err = Fetch.Do(request)
|
||||
return
|
||||
}
|
||||
|
||||
// Cookies does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Response) Cookies() []*Cookie { return nil }
|
||||
|
||||
// Location returns the location header (if present).
|
||||
func (r *Response) Location() (string, error) {
|
||||
if r.Header.Has("Location") {
|
||||
return r.Header.Get("Location"), nil
|
||||
} else {
|
||||
return "", errors.New("http: no Location header in response")
|
||||
}
|
||||
}
|
||||
|
||||
// ProtoAtLeast does nothing, but it is required for compatibility with the Go standard library.
|
||||
func (r *Response) ProtoAtLeast(major, minor int) bool { return true }
|
||||
|
||||
// Write writes an HTTP/1.1 response, which is the header and body, in wire format.
|
||||
// It is not yet implemented (and probably never will be, because after all, why would you need to write an HTTP response in wire format?).
|
||||
func (r *Response) Write(w io.Writer) error { return nil }
|
|
@ -0,0 +1,651 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Wasm-Tester</title>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
|
||||
/*
|
||||
* wasm_exec (https://github.com/golang/go)
|
||||
* (c) The Go Authors
|
||||
* @license BSD-3-Clause
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!globalThis.fs) {
|
||||
let outputBuf = "";
|
||||
globalThis.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substring(0, nl));
|
||||
outputBuf = outputBuf.substring(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
callback(enosys());
|
||||
return;
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
chmod(path, mode, callback) { callback(enosys()); },
|
||||
chown(path, uid, gid, callback) { callback(enosys()); },
|
||||
close(fd, callback) { callback(enosys()); },
|
||||
fchmod(fd, mode, callback) { callback(enosys()); },
|
||||
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
||||
fstat(fd, callback) { callback(enosys()); },
|
||||
fsync(fd, callback) { callback(null); },
|
||||
ftruncate(fd, length, callback) { callback(enosys()); },
|
||||
lchown(path, uid, gid, callback) { callback(enosys()); },
|
||||
link(path, link, callback) { callback(enosys()); },
|
||||
lstat(path, callback) { callback(enosys()); },
|
||||
mkdir(path, perm, callback) { callback(enosys()); },
|
||||
open(path, flags, mode, callback) { callback(enosys()); },
|
||||
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
||||
readdir(path, callback) { callback(enosys()); },
|
||||
readlink(path, callback) { callback(enosys()); },
|
||||
rename(from, to, callback) { callback(enosys()); },
|
||||
rmdir(path, callback) { callback(enosys()); },
|
||||
stat(path, callback) { callback(enosys()); },
|
||||
symlink(path, link, callback) { callback(enosys()); },
|
||||
truncate(path, length, callback) { callback(enosys()); },
|
||||
unlink(path, callback) { callback(enosys()); },
|
||||
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
||||
};
|
||||
}
|
||||
|
||||
if (!globalThis.process) {
|
||||
globalThis.process = {
|
||||
getuid() { return -1; },
|
||||
getgid() { return -1; },
|
||||
geteuid() { return -1; },
|
||||
getegid() { return -1; },
|
||||
getgroups() { throw enosys(); },
|
||||
pid: -1,
|
||||
ppid: -1,
|
||||
umask() { throw enosys(); },
|
||||
cwd() { throw enosys(); },
|
||||
chdir() { throw enosys(); },
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalThis.crypto) {
|
||||
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
||||
}
|
||||
|
||||
if (!globalThis.performance) {
|
||||
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
||||
}
|
||||
|
||||
if (!globalThis.TextEncoder) {
|
||||
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
if (!globalThis.TextDecoder) {
|
||||
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
globalThis.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this.exit = (code) => {
|
||||
if (code !== 0) {
|
||||
console.warn("exit code:", code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise((resolve) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const setInt32 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = this.mem.getUint32(addr + 0, true);
|
||||
const high = this.mem.getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = this.mem.getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = this.mem.getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number" && v !== 0) {
|
||||
if (isNaN(v)) {
|
||||
this.mem.setUint32(addr + 4, nanHead, true);
|
||||
this.mem.setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
this.mem.setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
this.mem.setFloat64(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = this._values.length;
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "object":
|
||||
if (v !== null) {
|
||||
typeFlag = 1;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4;
|
||||
break;
|
||||
}
|
||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
this.mem.setUint32(addr, id, true);
|
||||
}
|
||||
|
||||
const loadSlice = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (addr) => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
_gotest: {
|
||||
add: (a, b) => a + b,
|
||||
},
|
||||
gojs: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
"runtime.wasmExit": (sp) => {
|
||||
sp >>>= 0;
|
||||
const code = this.mem.getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._goRefCounts;
|
||||
delete this._ids;
|
||||
delete this._idPool;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
"runtime.wasmWrite": (sp) => {
|
||||
sp >>>= 0;
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = this.mem.getInt32(sp + 24, true);
|
||||
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||
},
|
||||
|
||||
// func resetMemoryDataView()
|
||||
"runtime.resetMemoryDataView": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
},
|
||||
|
||||
// func nanotime1() int64
|
||||
"runtime.nanotime1": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
"runtime.walltime": (sp) => {
|
||||
sp >>>= 0;
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(id, setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8),
|
||||
));
|
||||
this.mem.setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
"runtime.clearTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
"runtime.getRandomData": (sp) => {
|
||||
sp >>>= 0;
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getUint32(sp + 8, true);
|
||||
this._goRefCounts[id]--;
|
||||
if (this._goRefCounts[id] === 0) {
|
||||
const v = this._values[id];
|
||||
this._values[id] = null;
|
||||
this._ids.delete(v);
|
||||
this._idPool.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (sp) => {
|
||||
sp >>>= 0;
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
"syscall/js.copyBytesToJS": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
"debug": (value) => {
|
||||
console.log(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
if (!(instance instanceof WebAssembly.Instance)) {
|
||||
throw new Error("Go.run: WebAssembly.Instance expected");
|
||||
}
|
||||
this._inst = instance;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
globalThis,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
this._ids = new Map([ // mapping from JS values to reference ids
|
||||
[0, 1],
|
||||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[globalThis, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = (str) => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + "\0");
|
||||
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach((arg) => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
keys.forEach((key) => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach((ptr) => {
|
||||
this.mem.setUint32(offset, ptr, true);
|
||||
this.mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error("total length of command line and environment variables exceeds limit");
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// @license-end
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
// @license magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt LGPL-3.0
|
||||
|
||||
/*
|
||||
* jsStreams_tester
|
||||
* (c) Arzumify
|
||||
* @license LGPL-3.0
|
||||
*/
|
||||
|
||||
const go = new Go();
|
||||
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
|
||||
go.run(result.instance);
|
||||
})
|
||||
|
||||
document.addEventListener("DOMContentLoadd", async () => {
|
||||
// Give some time for the WASM to load
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
// Start test one: GET request to the server
|
||||
await tryGet("https://localhost:8080/hello")
|
||||
} catch (e) {
|
||||
// Test failed
|
||||
fetch("/reportTestResults", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Success": "false",
|
||||
"Error": e.message,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
// Test passed
|
||||
// Start test two: HEAD request to the server
|
||||
try {
|
||||
await tryHead("https://localhost:8080/hello")
|
||||
} catch (e) {
|
||||
// Test failed
|
||||
fetch("/reportTestResults", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Success": "false",
|
||||
"Error": e.message,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
// Test passed
|
||||
// Start test three: POST request to the server
|
||||
try {
|
||||
await tryPost("https://localhost:8080/hello", "Hello, World!")
|
||||
} catch (e) {
|
||||
// Test failed
|
||||
fetch("/reportTestResults", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Success": "false",
|
||||
"Error": e.message,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
// Test passed
|
||||
fetch("/reportTestResults", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Success": "true",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
})
|
||||
|
||||
// @license-end
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,120 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.ailur.dev/ailur/jsFetch"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
func tryGet(url string) error {
|
||||
response, err := jsFetch.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(response.StatusCode)
|
||||
read, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(read))
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryHead(url string) error {
|
||||
request, err := jsFetch.NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response, err := jsFetch.Fetch.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(response.StatusCode)
|
||||
fmt.Println(response.Header)
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryPost(url string, message string) error {
|
||||
response, err := jsFetch.Post(url, strings.NewReader(message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(response.StatusCode)
|
||||
read := make([]byte, len(message))
|
||||
_, err = response.Body.Read(read)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(read))
|
||||
return nil
|
||||
}
|
||||
|
||||
func reportTest(fail bool, err error) {
|
||||
request, _ := http.NewRequest("GET", "https://localhost:8080/reportTestResults", nil)
|
||||
request.Header.Set("Success", fmt.Sprint(!fail))
|
||||
if err != nil {
|
||||
request.Header.Set("Error", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
js.Global().Set("tryGet", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
|
||||
go func() {
|
||||
err := tryGet(p[0].String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}))
|
||||
|
||||
js.Global().Set("tryHead", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
|
||||
go func() {
|
||||
err := tryHead(p[0].String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}))
|
||||
|
||||
js.Global().Set("tryPost", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
|
||||
go func() {
|
||||
err := tryPost(p[0].String(), p[1].String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}))
|
||||
|
||||
js.Global().Set("tryAll", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
|
||||
go func() {
|
||||
err := tryGet("https://localhost:8080/hello")
|
||||
if err != nil {
|
||||
reportTest(true, err)
|
||||
return
|
||||
}
|
||||
err = tryHead("https://localhost:8080/hello")
|
||||
if err != nil {
|
||||
reportTest(true, err)
|
||||
return
|
||||
}
|
||||
err = tryPost("https://localhost:8080/hello", "Hello, world!")
|
||||
if err != nil {
|
||||
reportTest(true, err)
|
||||
return
|
||||
}
|
||||
reportTest(false, nil)
|
||||
}()
|
||||
return nil
|
||||
}))
|
||||
|
||||
select {}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, HEAD")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Success, Error")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
file, err := os.ReadFile("../client/index.html")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = w.Write(file)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
r.Get("/main.wasm", func(w http.ResponseWriter, r *http.Request) {
|
||||
file, err := os.ReadFile("../client/main.wasm")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = w.Write(file)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
r.Options("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, HEAD")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Success, Error")
|
||||
})
|
||||
r.Options("/reportTestResults", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, HEAD")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Success, Error")
|
||||
})
|
||||
r.Head("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.Header().Set("Success", "true")
|
||||
})
|
||||
r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := w.Write([]byte("hello"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
r.Post("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
_, err = w.Write(body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
r.Get("/reportTestResults", func(w http.ResponseWriter, r *http.Request) {
|
||||
success := r.Header.Get("Success")
|
||||
if success == "true" {
|
||||
fmt.Println("Test passed")
|
||||
os.Exit(0)
|
||||
} else {
|
||||
fmt.Println("Test failed... " + r.Header.Get("Error"))
|
||||
os.Exit(1)
|
||||
}
|
||||
})
|
||||
err := http.ListenAndServeTLS(":8080", "server.crt", "server.key", r)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#!/bin/sh
|
||||
|
||||
CA_FILE=$(realpath "./server/ca.crt")
|
||||
CA_KEY=$(realpath "./server/ca.key")
|
||||
SRL_FILE=$(realpath "./server/ca.srl")
|
||||
CSR_FILE=$(realpath "./server/server.csr")
|
||||
SSL_FILE=$(realpath "./server/server.crt")
|
||||
SSL_KEY=$(realpath "./server/server.key")
|
||||
|
||||
superuserCommand="pkexec"
|
||||
if [ -z "$(command -v pkexec)" ]; then
|
||||
superuserCommand="sudo"
|
||||
fi
|
||||
|
||||
if [ "$1" = "-u" ] || [ "$1" = "--uninstall" ]; then
|
||||
echo "Uninstalling the certificate..."
|
||||
if [ -z "$(command -v p11-kit)" ]; then
|
||||
$superuserCommand sh -c "rm /usr/local/share/ca-certificates/$CA_FILE && update-ca-certificates"
|
||||
else
|
||||
$superuserCommand sh -c "trust anchor --remove $CA_FILE"
|
||||
fi
|
||||
rm "$CA_FILE" "$CA_KEY" "$CSR_FILE" "$SSL_FILE" "$SSL_KEY" "$SRL_FILE"
|
||||
echo "Good, you've uninstalled the certificate."
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! [ -f "$CA_FILE" ] || ! [ -f "$CA_KEY" ] || ! [ -f "$CSR_FILE" ] || ! [ -f "$SSL_FILE" ] || ! [ -f "$SSL_KEY" ]; then
|
||||
echo "Warning! This will add a certificate to your system's trust store."
|
||||
echo "If this self-signed certificate is ever leaked, attackers can use it to cause damage."
|
||||
echo "Please only run this script if you understand the risks and trust the source of the certificate."
|
||||
echo "We take no responsibility for any damage caused by the use of this certificate... though that's said in the LICENSE."
|
||||
echo "Do you want to continue? (yes/no)"
|
||||
read -r answer
|
||||
if [ "$answer" != "yes" ]; then
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
echo "Well, you said it, not me."
|
||||
COUNTRY="GB"
|
||||
STATE="London"
|
||||
LOCALITY="London"
|
||||
ORGANIZATION="Totally Real Company Inc."
|
||||
ORGANIZATIONAL_UNIT="Testing Department"
|
||||
COMMON_NAME="localhost"
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout "$CA_KEY" -out "$CA_FILE" \
|
||||
-subj "/C=$COUNTRY/ST=$STATE/L=$LOCALITY/O=$ORGANIZATION/OU=$ORGANIZATIONAL_UNIT/CN=$COMMON_NAME"
|
||||
openssl req -nodes -newkey rsa:2048 \
|
||||
-keyout "$SSL_KEY" -out "$CSR_FILE" \
|
||||
-subj "/C=$COUNTRY/ST=$STATE/L=$LOCALITY/O=$ORGANIZATION/OU=$ORGANIZATIONAL_UNIT/CN=$COMMON_NAME"
|
||||
printf "subjectAltName = DNS:%s\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = CA:FALSE\nkeyUsage = digitalSignature, keyEncipherment\nextendedKeyUsage=serverAuth" $COMMON_NAME > /tmp/extfile.cnf
|
||||
openssl x509 -req -in "$CSR_FILE" -CA "$CA_FILE" -CAkey "$CA_KEY" -CAcreateserial -out "$SSL_FILE" -days 365 \
|
||||
-extfile /tmp/extfile.cnf
|
||||
echo "Self-signed certificate and key have been generated:"
|
||||
echo "Trusting the certificate... (you may be prompted for your password)".
|
||||
if [ -z "$(command -v p11-kit)" ]; then
|
||||
$superuserCommand sh -c "cp $CA_FILE /usr/local/share/ca-certificates/$CA_FILE && update-ca-certificates"
|
||||
else
|
||||
$superuserCommand sh -c "trust anchor $CA_FILE"
|
||||
fi
|
||||
echo "Deleting temporary files..."
|
||||
rm /tmp/extfile.cnf
|
||||
fi
|
||||
|
||||
echo "Building the server and client..."
|
||||
go build -o server/server server/main.go
|
||||
GOOS=js GOARCH=wasm go build -o client/main.wasm client/main.go
|
||||
echo "Launching the client in your default browser..."
|
||||
xdg-open "https://localhost:8080"
|
||||
echo "Launching the server..."
|
||||
cd server || exit 1
|
||||
echo "Server started. Press Ctrl+C to stop."
|
||||
./server
|
||||
echo "Alright, the server has stopped. If you want to remove the self-signed certificate, run ./test.sh --uninstall."
|
Loading…
Reference in New Issue