mirror of
https://github.com/openfaas/faasd.git
synced 2025-06-18 20:16:36 +00:00
Compare commits
7 Commits
bidirectio
...
0.4.3
Author | SHA1 | Date | |
---|---|---|---|
098baba7cc | |||
9d688b9ea6 | |||
1458a2f2fb | |||
c18a038062 | |||
af0555a85b | |||
2ff8646669 | |||
d785bebf4c |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
@alexellis
|
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!--- Provide a general summary of the issue in the Title above -->
|
||||||
|
|
||||||
|
## Expected Behaviour
|
||||||
|
<!--- If you're describing a bug, tell us what should happen -->
|
||||||
|
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||||
|
|
||||||
|
## Current Behaviour
|
||||||
|
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||||
|
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||||
|
|
||||||
|
## Possible Solution
|
||||||
|
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||||
|
<!--- or ideas how to implement the addition or change -->
|
||||||
|
|
||||||
|
## Steps to Reproduce (for bugs)
|
||||||
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||||
|
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||||
|
|
||||||
|
## Your Environment
|
||||||
|
|
||||||
|
* OS and architecture:
|
||||||
|
|
||||||
|
* Versions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go version
|
||||||
|
|
||||||
|
containerd -version
|
||||||
|
|
||||||
|
uname -a
|
||||||
|
|
||||||
|
cat /etc/os-release
|
||||||
|
```
|
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
40
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<!--- Provide a general summary of your changes in the Title above -->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!--- Describe your changes in detail -->
|
||||||
|
|
||||||
|
## Motivation and Context
|
||||||
|
<!--- Why is this change required? What problem does it solve? -->
|
||||||
|
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||||
|
- [ ] I have raised an issue to propose this change **this is required**
|
||||||
|
|
||||||
|
|
||||||
|
## How Has This Been Tested?
|
||||||
|
<!--- Please describe in detail how you tested your changes. -->
|
||||||
|
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||||
|
<!--- see how your change affects other areas of the code, etc. -->
|
||||||
|
|
||||||
|
|
||||||
|
## Types of changes
|
||||||
|
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||||
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
|
||||||
|
## Checklist:
|
||||||
|
|
||||||
|
Commits:
|
||||||
|
|
||||||
|
- [ ] I've read the [CONTRIBUTION](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md) guide
|
||||||
|
- [ ] My commit message has a body and describe how this was tested and why it is required.
|
||||||
|
- [ ] I have signed-off my commits with `git commit -s` for the Developer Certificate of Origin (DCO)
|
||||||
|
|
||||||
|
Code:
|
||||||
|
|
||||||
|
- [ ] My code follows the code style of this project.
|
||||||
|
- [ ] I have added tests to cover my changes.
|
||||||
|
|
||||||
|
Docs:
|
||||||
|
|
||||||
|
- [ ] My change requires a change to the documentation.
|
||||||
|
- [ ] I have updated the documentation accordingly.
|
25
README.md
25
README.md
@ -30,10 +30,20 @@ Other operations are pending development in the provider.
|
|||||||
|
|
||||||
### Pre-reqs
|
### Pre-reqs
|
||||||
|
|
||||||
* Linux - ideally Ubuntu, which is used for testing
|
* Linux
|
||||||
|
|
||||||
|
PC / Cloud - any Linux that containerd works on should be fair game, but faasd is tested with Ubuntu 18.04
|
||||||
|
|
||||||
|
For Raspberry Pi Raspbian Stretch or newer also works fine
|
||||||
|
|
||||||
|
For MacOS users try [multipass.run](https://multipass.run) or [Vagrant](https://www.vagrantup.com/)
|
||||||
|
|
||||||
|
For Windows users, install [Git Bash](https://git-scm.com/downloads) along with multipass or vagrant. You can also use WSL1 or WSL2 which provides a Linux environment.
|
||||||
|
|
||||||
* Installation steps as per [faas-containerd](https://github.com/alexellis/faas-containerd) for building and for development
|
* Installation steps as per [faas-containerd](https://github.com/alexellis/faas-containerd) for building and for development
|
||||||
* [netns](https://github.com/genuinetools/netns/releases) binary in `$PATH`
|
* [netns](https://github.com/genuinetools/netns/releases) binary in `$PATH`
|
||||||
* [containerd v1.3.2](https://github.com/containerd/containerd)
|
* [containerd v1.3.2](https://github.com/containerd/containerd)
|
||||||
|
|
||||||
* [faas-cli](https://github.com/openfaas/faas-cli) (optional)
|
* [faas-cli](https://github.com/openfaas/faas-cli) (optional)
|
||||||
|
|
||||||
## Backlog
|
## Backlog
|
||||||
@ -44,6 +54,7 @@ Pending:
|
|||||||
* [ ] Monitor and restart any of the core components at runtime if the container stops
|
* [ ] Monitor and restart any of the core components at runtime if the container stops
|
||||||
* [ ] Bundle/package/automate installation of containerd - [see bootstrap from k3s](https://github.com/rancher/k3s)
|
* [ ] Bundle/package/automate installation of containerd - [see bootstrap from k3s](https://github.com/rancher/k3s)
|
||||||
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
|
* [ ] Provide ufw rules / example for blocking access to everything but a reverse proxy to the gateway container
|
||||||
|
* [ ] Provide [simple Caddyfile example](https://blog.alexellis.io/https-inlets-local-endpoints/) in the README showing how to expose the faasd proxy on port 80/443 with TLS
|
||||||
|
|
||||||
Done:
|
Done:
|
||||||
|
|
||||||
@ -56,6 +67,12 @@ Done:
|
|||||||
* [x] Determine armhf/arm64 containers to run for gateway
|
* [x] Determine armhf/arm64 containers to run for gateway
|
||||||
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
* [x] Configure `basic_auth` to protect the OpenFaaS gateway and faas-containerd HTTP API
|
||||||
|
|
||||||
|
## Tutorial: Get started on armhf / Raspberry Pi
|
||||||
|
|
||||||
|
You can run this tutorial on your Raspberry Pi, or adapt the steps for a regular Linux VM/VPS host.
|
||||||
|
|
||||||
|
* [faasd - lightweight Serverless for your Raspberry Pi](https://blog.alexellis.io/faasd-for-lightweight-serverless/)
|
||||||
|
|
||||||
## Hacking (build from source)
|
## Hacking (build from source)
|
||||||
|
|
||||||
First run faas-containerd
|
First run faas-containerd
|
||||||
@ -86,17 +103,17 @@ go build
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# For x86_64
|
# For x86_64
|
||||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd" \
|
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.2/faasd" \
|
||||||
-o "/usr/local/bin/faasd" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
# armhf
|
# armhf
|
||||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd-armhf" \
|
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.2/faasd-armhf" \
|
||||||
-o "/usr/local/bin/faasd" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.3.1/faasd-arm64" \
|
sudo curl -fSLs "https://github.com/alexellis/faasd/releases/download/0.4.2/faasd-arm64" \
|
||||||
-o "/usr/local/bin/faasd" \
|
-o "/usr/local/bin/faasd" \
|
||||||
&& sudo chmod a+x "/usr/local/bin/faasd"
|
&& sudo chmod a+x "/usr/local/bin/faasd"
|
||||||
```
|
```
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
systemd "github.com/alexellis/faasd/pkg/systemd"
|
systemd "github.com/alexellis/faasd/pkg/systemd"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -18,6 +19,10 @@ var installCmd = &cobra.Command{
|
|||||||
|
|
||||||
func runInstall(_ *cobra.Command, _ []string) error {
|
func runInstall(_ *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
|
if basicAuthErr := makeBasicAuthFiles(); basicAuthErr != nil {
|
||||||
|
return errors.Wrap(basicAuthErr, "cannot create basic-auth-* files")
|
||||||
|
}
|
||||||
|
|
||||||
err := binExists("/usr/local/bin/", "faas-containerd")
|
err := binExists("/usr/local/bin/", "faas-containerd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
20
cmd/up.go
20
cmd/up.go
@ -26,6 +26,8 @@ var upCmd = &cobra.Command{
|
|||||||
RunE: runUp,
|
RunE: runUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const secretMountDir = "/run/secrets"
|
||||||
|
|
||||||
func runUp(_ *cobra.Command, _ []string) error {
|
func runUp(_ *cobra.Command, _ []string) error {
|
||||||
|
|
||||||
clientArch, clientOS := env.GetClientArch()
|
clientArch, clientOS := env.GetClientArch()
|
||||||
@ -47,9 +49,8 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
clientSuffix = "-arm64"
|
clientSuffix = "-arm64"
|
||||||
}
|
}
|
||||||
|
|
||||||
authFileErr := errors.Wrap(makeBasicAuthFiles(), "Could not create gateway auth files")
|
if basicAuthErr := makeBasicAuthFiles(); basicAuthErr != nil {
|
||||||
if authFileErr != nil {
|
return errors.Wrap(basicAuthErr, "cannot create basic-auth-* files")
|
||||||
return authFileErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services := makeServiceDefinitions(clientSuffix)
|
services := makeServiceDefinitions(clientSuffix)
|
||||||
@ -76,6 +77,7 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
|
|
||||||
shutdownTimeout := time.Second * 1
|
shutdownTimeout := time.Second * 1
|
||||||
timeout := time.Second * 60
|
timeout := time.Second * 60
|
||||||
|
proxyDoneCh := make(chan bool)
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
@ -91,14 +93,18 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close proxy
|
||||||
|
proxyDoneCh <- true
|
||||||
time.AfterFunc(shutdownTimeout, func() {
|
time.AfterFunc(shutdownTimeout, func() {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gatewayURLChan := make(chan string, 1)
|
gatewayURLChan := make(chan string, 1)
|
||||||
proxy := pkg.NewProxy(timeout)
|
proxyPort := 8080
|
||||||
go proxy.Start(gatewayURLChan)
|
proxy := pkg.NewProxy(proxyPort, timeout)
|
||||||
|
go proxy.Start(gatewayURLChan, proxyDoneCh)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
@ -118,7 +124,7 @@ func runUp(_ *cobra.Command, _ []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("[up] Sending %s to proxy\n", host)
|
log.Printf("[up] Sending %s to proxy\n", host)
|
||||||
gatewayURLChan <- host
|
gatewayURLChan <- host + ":8080"
|
||||||
close(gatewayURLChan)
|
close(gatewayURLChan)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -165,8 +171,6 @@ func makeFile(filePath, fileContents string) error {
|
|||||||
func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
func makeServiceDefinitions(archSuffix string) []pkg.Service {
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
secretMountDir := "/run/secrets"
|
|
||||||
|
|
||||||
return []pkg.Service{
|
return []pkg.Service{
|
||||||
pkg.Service{
|
pkg.Service{
|
||||||
Name: "basic-auth-plugin",
|
Name: "basic-auth-plugin",
|
||||||
|
123
pkg/proxy.go
123
pkg/proxy.go
@ -1,6 +1,7 @@
|
|||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -10,83 +11,58 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewProxy(timeout time.Duration) *Proxy {
|
func NewProxy(port int, timeout time.Duration) *Proxy {
|
||||||
|
|
||||||
return &Proxy{
|
return &Proxy{
|
||||||
|
Port: port,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
Port int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) Start(gatewayChan chan string) error {
|
func (p *Proxy) Start(gatewayChan chan string, done chan bool) error {
|
||||||
tcp := 8080
|
tcp := p.Port
|
||||||
|
|
||||||
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
}
|
}
|
||||||
|
ps := proxyState{
|
||||||
data := struct{ host string }{
|
Host: "",
|
||||||
host: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data.host = <-gatewayChan
|
ps.Host = <-gatewayChan
|
||||||
|
|
||||||
log.Printf("Starting faasd proxy on %d\n", tcp)
|
log.Printf("Starting faasd proxy on %d\n", tcp)
|
||||||
|
|
||||||
fmt.Printf("Gateway: %s\n", data.host)
|
fmt.Printf("Gateway: %s\n", ps.Host)
|
||||||
|
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Addr: fmt.Sprintf(":%d", tcp),
|
Addr: fmt.Sprintf(":%d", tcp),
|
||||||
ReadTimeout: p.Timeout,
|
ReadTimeout: p.Timeout,
|
||||||
WriteTimeout: p.Timeout,
|
WriteTimeout: p.Timeout,
|
||||||
MaxHeaderBytes: 1 << 20, // Max header of 1MB
|
MaxHeaderBytes: 1 << 20, // Max header of 1MB
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
Handler: http.HandlerFunc(makeProxy(&ps)),
|
||||||
|
|
||||||
query := ""
|
|
||||||
if len(r.URL.RawQuery) > 0 {
|
|
||||||
query = "?" + r.URL.RawQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
upstream := fmt.Sprintf("http://%s:8080%s%s", data.host, r.URL.Path, query)
|
|
||||||
fmt.Printf("[faasd] proxy: %s\n", upstream)
|
|
||||||
|
|
||||||
if r.Body != nil {
|
|
||||||
defer r.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper := ioutil.NopCloser(r.Body)
|
|
||||||
upReq, upErr := http.NewRequest(r.Method, upstream, wrapper)
|
|
||||||
|
|
||||||
copyHeaders(upReq.Header, &r.Header)
|
|
||||||
|
|
||||||
if upErr != nil {
|
|
||||||
log.Println(upErr)
|
|
||||||
|
|
||||||
http.Error(w, upErr.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
upRes, upResErr := http.DefaultClient.Do(upReq)
|
|
||||||
|
|
||||||
if upResErr != nil {
|
|
||||||
log.Println(upResErr)
|
|
||||||
|
|
||||||
http.Error(w, upResErr.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
copyHeaders(w.Header(), &upRes.Header)
|
|
||||||
|
|
||||||
w.WriteHeader(upRes.StatusCode)
|
|
||||||
io.Copy(w, upRes.Body)
|
|
||||||
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.ListenAndServe()
|
go func() {
|
||||||
|
log.Printf("[proxy] Begin listen on %d\n", p.Port)
|
||||||
|
if err := s.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
|
log.Printf("Error ListenAndServe: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("[proxy] Wait for done")
|
||||||
|
<-done
|
||||||
|
log.Println("[proxy] Done received")
|
||||||
|
if err := s.Shutdown(context.Background()); err != nil {
|
||||||
|
log.Printf("[proxy] Error in Shutdown: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyHeaders clones the header values from the source into the destination.
|
// copyHeaders clones the header values from the source into the destination.
|
||||||
@ -97,3 +73,50 @@ func copyHeaders(destination http.Header, source *http.Header) {
|
|||||||
destination[k] = vClone
|
destination[k] = vClone
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type proxyState struct {
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeProxy(ps *proxyState) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
query := ""
|
||||||
|
if len(r.URL.RawQuery) > 0 {
|
||||||
|
query = "?" + r.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream := fmt.Sprintf("http://%s%s%s", ps.Host, r.URL.Path, query)
|
||||||
|
fmt.Printf("[faasd] proxy: %s\n", upstream)
|
||||||
|
|
||||||
|
if r.Body != nil {
|
||||||
|
defer r.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := ioutil.NopCloser(r.Body)
|
||||||
|
upReq, upErr := http.NewRequest(r.Method, upstream, wrapper)
|
||||||
|
|
||||||
|
copyHeaders(upReq.Header, &r.Header)
|
||||||
|
|
||||||
|
if upErr != nil {
|
||||||
|
log.Println(upErr)
|
||||||
|
|
||||||
|
http.Error(w, upErr.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
upRes, upResErr := http.DefaultClient.Do(upReq)
|
||||||
|
|
||||||
|
if upResErr != nil {
|
||||||
|
log.Println(upResErr)
|
||||||
|
|
||||||
|
http.Error(w, upResErr.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
copyHeaders(w.Header(), &upRes.Header)
|
||||||
|
|
||||||
|
w.WriteHeader(upRes.StatusCode)
|
||||||
|
io.Copy(w, upRes.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
73
pkg/proxy_test.go
Normal file
73
pkg/proxy_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Proxy_ToPrivateServer(t *testing.T) {
|
||||||
|
|
||||||
|
wantBodyText := "OK"
|
||||||
|
wantBody := []byte(wantBodyText)
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
if r.Body != nil {
|
||||||
|
defer r.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(wantBody)
|
||||||
|
|
||||||
|
}))
|
||||||
|
|
||||||
|
defer upstream.Close()
|
||||||
|
port := 8080
|
||||||
|
proxy := NewProxy(port, time.Second*1)
|
||||||
|
|
||||||
|
gwChan := make(chan string, 1)
|
||||||
|
doneCh := make(chan bool)
|
||||||
|
|
||||||
|
go proxy.Start(gwChan, doneCh)
|
||||||
|
|
||||||
|
u, _ := url.Parse(upstream.URL)
|
||||||
|
log.Println("Host", u.Host)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
gwChan <- u.Host
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://127.0.0.1:%d", port), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < 11; i++ {
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Try %d, gave error: %s", i, err)
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
resBody, _ := ioutil.ReadAll(res.Body)
|
||||||
|
if string(resBody) != string(wantBody) {
|
||||||
|
t.Errorf("want %s, but got %s in body", string(wantBody), string(resBody))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
doneCh <- true
|
||||||
|
}()
|
||||||
|
}
|
Reference in New Issue
Block a user