diff --git a/Makefile b/Makefile index 05663270..ecce2d5a 100644 --- a/Makefile +++ b/Makefile @@ -238,8 +238,15 @@ test: generate-cert LD_LIBRARY_PATH=$(OUTPUT_PATH)/lib COLONIO_SEED_BIN_PATH=$(OUTPUT_PATH)/seed \ COLONIO_TEST_CERT=$(ROOT_PATH)/localhost.crt COLONIO_TEST_PRIVATE_KEY=$(ROOT_PATH)/localhost.key \ $(MAKE) -C $(NATIVE_BUILD_PATH) CTEST_OUTPUT_ON_FAILURE=1 test ARGS='$(CTEST_ARGS)' + # JS + if [ $(shell uname -s) = "Linux" ]; then $(MAKE) test-js-browser; fi # golang $(MAKE) test-go-native + if [ $(shell uname -s) = "Linux" ]; then $(MAKE) test-go-wasm; fi + +.PHONY: test-js-browser +test-js-browser: + go run $(ROOT_PATH)/go/cmd/test-luncher/ -c $(ROOT_PATH)/test/js/seed.json -f FAILURE .PHONY: test-go-native test-go-native: build-seed @@ -251,7 +258,7 @@ test-go-native: build-seed test-go-wasm: build-seed cp $(shell go env GOROOT)/misc/wasm/wasm_exec.js ./go/test GOOS=js GOARCH=wasm go test -c -o ./go/test/test.wasm ./go/test/ - $(OUTPUT_PATH)/seed -c $(ROOT_PATH)/go/test/seed.json + go run $(ROOT_PATH)/go/cmd/test-luncher/ -c $(ROOT_PATH)/go/test/seed.json -s PASS .PHONY: format-code format-code: diff --git a/ci.sh b/ci.sh index 747b8e45..507e7466 100755 --- a/ci.sh +++ b/ci.sh @@ -8,7 +8,11 @@ readonly OS=$(uname -s) if [ "${OS}" = "Linux" ]; then if [ "${ARCH}" = "x86_64" ]; then # linux x86_64 - sudo apt install cmake clang-format valgrind libcurl3-nss libcurl4-nss-dev + # config apt to install google chrome + sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' + sudo wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo apt update + sudo apt install cmake clang-format valgrind libcurl3-nss libcurl4-nss-dev google-chrome-stable pip3 install --user cpp-coveralls # check format @@ -24,6 +28,8 @@ if [ "${OS}" = "Linux" ]; then sudo sysctl -w net.core.rmem_max=2500000 make test CTEST_ARGS="--output-on-failure -T memcheck --overwrite MemoryCheckCommandOptions=\"--error-exitcode=1 --leak-check=full --suppressions=$(pwd)/valgrind.supp\"" + make test-js-browser + make test-go-wasm export PATH=$PATH:$(python3 -m site --user-base)/bin coveralls -b ./build/linux_x86_64/test/CMakeFiles/colonio_test.dir/__/ -i src -e src/js -E '.*\.pb\.h' -E '.*\.pb\.cc' --gcov-options '\-lp' exit 0 diff --git a/go.mod b/go.mod index 56a70211..51ae3714 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/llamerada-jp/colonio go 1.20 require ( + github.com/chromedp/cdproto v0.0.0-20230611221135-4cd95c996604 + github.com/chromedp/chromedp v0.9.1 github.com/quic-go/quic-go v0.35.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -11,12 +13,18 @@ require ( ) require ( + github.com/chromedp/sysutil v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.1.0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/onsi/ginkgo/v2 v2.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -26,7 +34,7 @@ require ( golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.6.0 // indirect golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect + golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.2.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 56ed8ac0..a160a1ab 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,10 @@ +github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/cdproto v0.0.0-20230611221135-4cd95c996604 h1:RkEDg4z76ah0BZ1yYlgDbz7Jlm2x5WCgycbhBf3GCQE= +github.com/chromedp/cdproto v0.0.0-20230611221135-4cd95c996604/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.1 h1:CC7cC5p1BeLiiS2gfNNPwp3OaUxtRMBjfiw3E3k6dFA= +github.com/chromedp/chromedp v0.9.1/go.mod h1:DUgZWRvYoEfgi66CgZ/9Yv+psgi+Sksy5DTScENWjaQ= +github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -8,6 +15,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= +github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -19,13 +32,21 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -69,10 +90,11 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/go/cmd/seed/main.go b/go/cmd/seed/main.go index ea5af0bf..7e027fec 100644 --- a/go/cmd/seed/main.go +++ b/go/cmd/seed/main.go @@ -19,36 +19,18 @@ package main import ( "context" "encoding/json" - "errors" "flag" - "fmt" "log" - "math/rand" - "net/http" "os" "path/filepath" - "github.com/llamerada-jp/colonio/go/seed" - "github.com/quic-go/quic-go/http3" + "github.com/llamerada-jp/colonio/go/service" "github.com/spf13/cobra" "github.com/spf13/pflag" ) var configFile string -type config struct { - seed.Config - - DocumentRoot *string `json:"documentRoot,omitempty"` - Headers map[string]string `json:"headers"` - Overrides map[string]string `json:"overrides"` - Port uint16 `json:"port"` - Path string `json:"path"` - CertFile string `json:"certFile"` - KeyFile string `json:"keyFile"` - UseTCP bool `json:"useTcp"` -} - var cmd = &cobra.Command{ Use: "seed", PersistentPreRun: func(cmd *cobra.Command, args []string) { @@ -56,133 +38,26 @@ var cmd = &cobra.Command{ }, RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true - mux := http.NewServeMux() - baseDir := filepath.Dir(configFile) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() // read config file configData, err := os.ReadFile(configFile) if err != nil { return err } - config := &config{} + config := &service.Config{} if err := json.Unmarshal(configData, config); err != nil { return err } - if err := validateConf(config); err != nil { - return err - } - - // Set HTTP headers to enable SharedArrayBuffer for WebAssembly Threads - headers := config.Headers - if headers == nil { - headers = make(map[string]string) - } - headers["Cross-Origin-Opener-Policy"] = "same-origin" - headers["Cross-Origin-Embedder-Policy"] = "require-corp" - - // Publish static documents - if config.DocumentRoot != nil { - log.Printf("publish DocumentRoot: %s", *config.DocumentRoot) - mux.Handle("/", http.FileServer(http.Dir(calcPath(baseDir, *config.DocumentRoot)))) - for pattern, path := range config.Overrides { - mux.Handle(pattern, http.FileServer(http.Dir(calcPath(baseDir, path)))) - } - } - - // Publish colonio-seed - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - seed, err := seed.NewSeedHandler(ctx, &config.Config, nil) - if err != nil { - return err - } - mux.Handle(config.Path+"/", http.StripPrefix(config.Path, seed)) - - // Start HTTP/3 service. - if config.UseTCP { - log.Println("start seed with tcp mode") - return http3.ListenAndServe( - fmt.Sprintf(":%d", config.Port), - calcPath(baseDir, config.CertFile), - calcPath(baseDir, config.KeyFile), - &handlerWrapper{ - handler: mux, - headers: headers, - }) - } - - log.Println("start seed") - server := http3.Server{ - Handler: mux, - Addr: fmt.Sprintf(":%d", config.Port), - } - return server.ListenAndServeTLS( - calcPath(baseDir, config.CertFile), - calcPath(baseDir, config.KeyFile)) + // start service of the seed + baseDir := filepath.Dir(configFile) + return service.Run(ctx, baseDir, config, nil) }, } -func validateConf(c *config) error { - if len(c.CertFile) == 0 { - return errors.New("config value of `certFile` required") - } - - if len(c.KeyFile) == 0 { - return errors.New("config value of `keyFile` required") - } - - return nil -} - -func calcPath(baseDir, path string) string { - if filepath.IsAbs(path) { - return path - } - return filepath.Join(baseDir, path) -} - -// warp http response writer to get http status code -type logWrapper struct { - http.ResponseWriter - statusCode int -} - -func (lw *logWrapper) WriteHeader(statusCode int) { - lw.statusCode = statusCode - lw.ResponseWriter.WriteHeader(statusCode) -} - -// warp http handler to add some headers and to output access logs -type handlerWrapper struct { - handler http.Handler - headers map[string]string -} - -func (h *handlerWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { - requestID := fmt.Sprintf("(%016x)", rand.Int63()) - log.Printf("%s proto:%s, remote addr:%s, method:%s uri:%s size:%d\n", requestID, r.Proto, r.RemoteAddr, r.Method, r.RequestURI, r.ContentLength) - - writerWithLog := &logWrapper{ - ResponseWriter: w, - } - defer func() { - log.Printf("%s status code:%d", requestID, writerWithLog.statusCode) - }() - - headerWriter := w.Header() - for k, v := range h.headers { - headerWriter.Add(k, v) - } - - // set request id for the context - ctx := context.WithValue(r.Context(), seed.CONTEXT_REQUEST_KEY, requestID) - r = r.WithContext(ctx) - - h.handler.ServeHTTP(writerWithLog, r) -} - func init() { flags := cmd.PersistentFlags() diff --git a/go/cmd/test-luncher/main.go b/go/cmd/test-luncher/main.go new file mode 100644 index 00000000..92fc6ec9 --- /dev/null +++ b/go/cmd/test-luncher/main.go @@ -0,0 +1,126 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/chromedp/cdproto/runtime" + "github.com/chromedp/chromedp" + "github.com/llamerada-jp/colonio/go/service" + "github.com/spf13/cobra" +) + +/* + * Copyright 2017 Yuji Ito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var configFile string +var url string +var keyword_success string +var keyword_failure string + +var cmd = &cobra.Command{ + Use: "test_luncher", + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + opts := append(chromedp.DefaultExecAllocatorOptions[:], + chromedp.Flag("ignore-certificate-errors", "1"), + ) + allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) + defer cancel() + ctx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf)) + defer cancel() + + // read config file + configData, err := os.ReadFile(configFile) + if err != nil { + return err + } + config := &service.Config{} + if err := json.Unmarshal(configData, config); err != nil { + return err + } + + // start service of the seed + go func() { + baseDir := filepath.Dir(configFile) + if err := service.Run(ctx, baseDir, config, nil); err != nil { + log.Fatal(err) + } + }() + + // wait for keyword to stop + finCh := make(chan bool, 1) + chromedp.ListenTarget(ctx, func(ev interface{}) { + switch ev := ev.(type) { + case *runtime.EventConsoleAPICalled: + for _, arg := range ev.Args { + if arg.Type != runtime.TypeString { + continue + } + var line string + if err := json.Unmarshal(arg.Value, &line); err != nil { + log.Println("failed to decode: ", line) + } + log.Println("(browser)", line) + switch line { + case keyword_success: + finCh <- true + + case keyword_failure: + finCh <- false + } + } + + case *runtime.EventExceptionThrown: + s := ev.ExceptionDetails.Error() + fmt.Printf("* %s\n", s) + } + }) + + // run test in headless browser + if err := chromedp.Run(ctx, chromedp.Navigate(url)); err != nil { + return err + } + + // get result of the test + result := <-finCh + if !result { + return fmt.Errorf("test failed") + } + + return nil + }, +} + +func init() { + flags := cmd.PersistentFlags() + flags.StringVarP(&configFile, "config", "c", "seed.json", "path to configuration file") + flags.StringVarP(&url, "url", "u", "https://localhost:8080/index.html", "URL to access to start the test") + flags.StringVarP(&keyword_success, "success", "s", "SUCCESS", "keyword will be output when the test succeeds") + flags.StringVarP(&keyword_failure, "failure", "f", "FAIL", "keyword will be output when the test fails") +} + +func main() { + if err := cmd.Execute(); err != nil { + log.Println(err) + os.Exit(1) + } +} diff --git a/go/service/config.go b/go/service/config.go new file mode 100644 index 00000000..32c34e14 --- /dev/null +++ b/go/service/config.go @@ -0,0 +1,47 @@ +/* + * Copyright 2019-2021 Yuji Ito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package service + +import ( + "errors" + + "github.com/llamerada-jp/colonio/go/seed" +) + +type Config struct { + seed.Config + + DocumentRoot *string `json:"documentRoot"` + Headers map[string]string `json:"headers"` + Overrides map[string]string `json:"overrides"` + Port uint16 `json:"port"` + Path string `json:"path"` + CertFile string `json:"certFile"` + KeyFile string `json:"keyFile"` + UseTCP bool `json:"useTcp"` +} + +func (c *Config) validate() error { + if len(c.CertFile) == 0 { + return errors.New("config value of `certFile` required") + } + + if len(c.KeyFile) == 0 { + return errors.New("config value of `keyFile` required") + } + + return nil +} diff --git a/go/service/service.go b/go/service/service.go new file mode 100644 index 00000000..62c7e448 --- /dev/null +++ b/go/service/service.go @@ -0,0 +1,124 @@ +/* + * Copyright 2019-2021 Yuji Ito + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package service + +import ( + "context" + "fmt" + "log" + "math/rand" + "net/http" + "path/filepath" + + "github.com/llamerada-jp/colonio/go/seed" + "github.com/quic-go/quic-go/http3" +) + +func Run(ctx context.Context, baseDir string, config *Config, verifier seed.TokenVerifier) error { + mux := http.NewServeMux() + + if err := config.validate(); err != nil { + return err + } + + // Publish static documents + if config.DocumentRoot != nil { + log.Printf("publish DocumentRoot: %s", *config.DocumentRoot) + mux.Handle("/", http.FileServer(http.Dir(calcPath(baseDir, *config.DocumentRoot)))) + for pattern, path := range config.Overrides { + mux.Handle(pattern, http.FileServer(http.Dir(calcPath(baseDir, path)))) + } + } + + // Publish colonio-seed + seed, err := seed.NewSeedHandler(ctx, &config.Config, verifier) + if err != nil { + return err + } + mux.Handle(config.Path+"/", http.StripPrefix(config.Path, seed)) + + // Start HTTP/3 service. + if config.UseTCP { + log.Println("start seed with tcp mode") + return http3.ListenAndServe( + fmt.Sprintf(":%d", config.Port), + calcPath(baseDir, config.CertFile), + calcPath(baseDir, config.KeyFile), + &handlerWrapper{ + handler: mux, + headers: config.Headers, + }) + } + + log.Println("start seed") + server := http3.Server{ + Handler: mux, + Addr: fmt.Sprintf(":%d", config.Port), + } + return server.ListenAndServeTLS( + calcPath(baseDir, config.CertFile), + calcPath(baseDir, config.KeyFile)) +} + +// warp http response writer to get http status code +type logWrapper struct { + http.ResponseWriter + statusCode int +} + +func (lw *logWrapper) WriteHeader(statusCode int) { + lw.statusCode = statusCode + lw.ResponseWriter.WriteHeader(statusCode) +} + +// warp http handler to add some headers and to output access logs +type handlerWrapper struct { + handler http.Handler + headers map[string]string +} + +func (h *handlerWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { + requestID := fmt.Sprintf("(%016x)", rand.Int63()) + log.Printf("%s proto:%s, remote addr:%s, method:%s uri:%s size:%d\n", requestID, r.Proto, r.RemoteAddr, r.Method, r.RequestURI, r.ContentLength) + + writerWithLog := &logWrapper{ + ResponseWriter: w, + } + defer func() { + log.Printf("%s status code:%d", requestID, writerWithLog.statusCode) + }() + + // Set HTTP headers to enable SharedArrayBuffer for WebAssembly Threads + header := w.Header() + header.Add("Cross-Origin-Opener-Policy", "same-origin") + header.Add("Cross-Origin-Embedder-Policy", "require-corp") + for k, v := range h.headers { + header.Add(k, v) + } + + // set request id for the context + ctx := context.WithValue(r.Context(), seed.CONTEXT_REQUEST_KEY, requestID) + r = r.WithContext(ctx) + + h.handler.ServeHTTP(writerWithLog, r) +} + +func calcPath(baseDir, path string) string { + if filepath.IsAbs(path) { + return path + } + return filepath.Join(baseDir, path) +} diff --git a/test/js/test.js b/test/js/test.js index a3e8ffdb..21a860d3 100644 --- a/test/js/test.js +++ b/test/js/test.js @@ -277,11 +277,13 @@ function test() { }).then(() => { let result = document.getElementById("result"); result.innerText = "SUCCESS"; + console.log("SUCCESS"); }).catch((e) => { let result = document.getElementById("result"); result.innerText = "FAILURE"; console.error(e); + console.log("FAILURE"); }); }