mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-07-29 05:29:56 +00:00
TUN-528: Move cloudflared into a separate repo
This commit is contained in:
110
vendor/github.com/mholt/caddy/caddyhttp/websocket/setup.go
generated
vendored
Normal file
110
vendor/github.com/mholt/caddy/caddyhttp/websocket/setup.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// 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 websocket
|
||||
|
||||
import (
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterPlugin("websocket", caddy.Plugin{
|
||||
ServerType: "http",
|
||||
Action: setup,
|
||||
})
|
||||
}
|
||||
|
||||
// setup configures a new WebSocket middleware instance.
|
||||
func setup(c *caddy.Controller) error {
|
||||
websocks, err := webSocketParse(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
GatewayInterface = caddy.AppName + "-CGI/1.1"
|
||||
ServerSoftware = caddy.AppName + "/" + caddy.AppVersion
|
||||
|
||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||
return WebSocket{Next: next, Sockets: websocks}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func webSocketParse(c *caddy.Controller) ([]Config, error) {
|
||||
var websocks []Config
|
||||
var respawn bool
|
||||
|
||||
optionalBlock := func() (hadBlock bool, err error) {
|
||||
for c.NextBlock() {
|
||||
hadBlock = true
|
||||
if c.Val() == "respawn" {
|
||||
respawn = true
|
||||
} else {
|
||||
return true, c.Err("Expected websocket configuration parameter in block")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for c.Next() {
|
||||
var val, path, command string
|
||||
|
||||
// Path or command; not sure which yet
|
||||
if !c.NextArg() {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
val = c.Val()
|
||||
|
||||
// Extra configuration may be in a block
|
||||
hadBlock, err := optionalBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !hadBlock {
|
||||
// The next argument on this line will be the command or an open curly brace
|
||||
if c.NextArg() {
|
||||
path = val
|
||||
command = c.Val()
|
||||
} else {
|
||||
path = "/"
|
||||
command = val
|
||||
}
|
||||
|
||||
// Okay, check again for optional block
|
||||
_, err = optionalBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Split command into the actual command and its arguments
|
||||
cmd, args, err := caddy.SplitCommandAndArgs(command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
websocks = append(websocks, Config{
|
||||
Path: path,
|
||||
Command: cmd,
|
||||
Arguments: args,
|
||||
Respawn: respawn, // TODO: This isn't used currently
|
||||
})
|
||||
}
|
||||
|
||||
return websocks, nil
|
||||
|
||||
}
|
117
vendor/github.com/mholt/caddy/caddyhttp/websocket/setup_test.go
generated
vendored
Normal file
117
vendor/github.com/mholt/caddy/caddyhttp/websocket/setup_test.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// 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 websocket
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
func TestWebSocket(t *testing.T) {
|
||||
c := caddy.NewTestController("http", `websocket cat`)
|
||||
err := setup(c)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no errors, got: %v", err)
|
||||
}
|
||||
mids := httpserver.GetConfig(c).Middleware()
|
||||
if len(mids) == 0 {
|
||||
t.Fatal("Expected middleware, got 0 instead")
|
||||
}
|
||||
|
||||
handler := mids[0](httpserver.EmptyNext)
|
||||
myHandler, ok := handler.(WebSocket)
|
||||
|
||||
if !ok {
|
||||
t.Fatalf("Expected handler to be type WebSocket, got: %#v", handler)
|
||||
}
|
||||
|
||||
if myHandler.Sockets[0].Path != "/" {
|
||||
t.Errorf("Expected / as the default Path")
|
||||
}
|
||||
if myHandler.Sockets[0].Command != "cat" {
|
||||
t.Errorf("Expected %s as the command", "cat")
|
||||
}
|
||||
|
||||
}
|
||||
func TestWebSocketParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
inputWebSocketConfig string
|
||||
shouldErr bool
|
||||
expectedWebSocketConfig []Config
|
||||
}{
|
||||
{`websocket /api1 cat`, false, []Config{{
|
||||
Path: "/api1",
|
||||
Command: "cat",
|
||||
}}},
|
||||
|
||||
{`websocket /api3 cat
|
||||
websocket /api4 cat `, false, []Config{{
|
||||
Path: "/api3",
|
||||
Command: "cat",
|
||||
}, {
|
||||
Path: "/api4",
|
||||
Command: "cat",
|
||||
}}},
|
||||
|
||||
{`websocket /api5 "cmd arg1 arg2 arg3"`, false, []Config{{
|
||||
Path: "/api5",
|
||||
Command: "cmd",
|
||||
Arguments: []string{"arg1", "arg2", "arg3"},
|
||||
}}},
|
||||
|
||||
// accept respawn
|
||||
{`websocket /api6 cat {
|
||||
respawn
|
||||
}`, false, []Config{{
|
||||
Path: "/api6",
|
||||
Command: "cat",
|
||||
}}},
|
||||
|
||||
// invalid configuration
|
||||
{`websocket /api7 cat {
|
||||
invalid
|
||||
}`, true, []Config{}},
|
||||
}
|
||||
for i, test := range tests {
|
||||
c := caddy.NewTestController("http", test.inputWebSocketConfig)
|
||||
actualWebSocketConfigs, err := webSocketParse(c)
|
||||
|
||||
if err == nil && test.shouldErr {
|
||||
t.Errorf("Test %d didn't error, but it should have", i)
|
||||
} else if err != nil && !test.shouldErr {
|
||||
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
|
||||
}
|
||||
if len(actualWebSocketConfigs) != len(test.expectedWebSocketConfig) {
|
||||
t.Fatalf("Test %d expected %d no of WebSocket configs, but got %d ",
|
||||
i, len(test.expectedWebSocketConfig), len(actualWebSocketConfigs))
|
||||
}
|
||||
for j, actualWebSocketConfig := range actualWebSocketConfigs {
|
||||
|
||||
if actualWebSocketConfig.Path != test.expectedWebSocketConfig[j].Path {
|
||||
t.Errorf("Test %d expected %dth WebSocket Config Path to be %s , but got %s",
|
||||
i, j, test.expectedWebSocketConfig[j].Path, actualWebSocketConfig.Path)
|
||||
}
|
||||
|
||||
if actualWebSocketConfig.Command != test.expectedWebSocketConfig[j].Command {
|
||||
t.Errorf("Test %d expected %dth WebSocket Config Command to be %s , but got %s",
|
||||
i, j, test.expectedWebSocketConfig[j].Command, actualWebSocketConfig.Command)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
275
vendor/github.com/mholt/caddy/caddyhttp/websocket/websocket.go
generated
vendored
Normal file
275
vendor/github.com/mholt/caddy/caddyhttp/websocket/websocket.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// 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 websocket implements a WebSocket server by executing
|
||||
// a command and piping its input and output through the WebSocket
|
||||
// connection.
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write a message to the peer.
|
||||
writeWait = 10 * time.Second
|
||||
|
||||
// Time allowed to read the next pong message from the peer.
|
||||
pongWait = 60 * time.Second
|
||||
|
||||
// Send pings to peer with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
|
||||
// Maximum message size allowed from peer.
|
||||
maxMessageSize = 1024 * 1024 * 10 // 10 MB default.
|
||||
)
|
||||
|
||||
var (
|
||||
// GatewayInterface is the dialect of CGI being used by the server
|
||||
// to communicate with the script. See CGI spec, 4.1.4
|
||||
GatewayInterface string
|
||||
|
||||
// ServerSoftware is the name and version of the information server
|
||||
// software making the CGI request. See CGI spec, 4.1.17
|
||||
ServerSoftware string
|
||||
)
|
||||
|
||||
type (
|
||||
// WebSocket is a type that holds configuration for the
|
||||
// websocket middleware generally, like a list of all the
|
||||
// websocket endpoints.
|
||||
WebSocket struct {
|
||||
// Next is the next HTTP handler in the chain for when the path doesn't match
|
||||
Next httpserver.Handler
|
||||
|
||||
// Sockets holds all the web socket endpoint configurations
|
||||
Sockets []Config
|
||||
}
|
||||
|
||||
// Config holds the configuration for a single websocket
|
||||
// endpoint which may serve multiple websocket connections.
|
||||
Config struct {
|
||||
Path string
|
||||
Command string
|
||||
Arguments []string
|
||||
Respawn bool // TODO: Not used, but parser supports it until we decide on it
|
||||
}
|
||||
)
|
||||
|
||||
// ServeHTTP converts the HTTP request to a WebSocket connection and serves it up.
|
||||
func (ws WebSocket) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
for _, sockconfig := range ws.Sockets {
|
||||
if httpserver.Path(r.URL.Path).Matches(sockconfig.Path) {
|
||||
return serveWS(w, r, &sockconfig)
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't match a websocket path, so pass-through
|
||||
return ws.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// serveWS is used for setting and upgrading the HTTP connection to a websocket connection.
|
||||
// It also spawns the child process that is associated with matched HTTP path/url.
|
||||
func serveWS(w http.ResponseWriter, r *http.Request, config *Config) (int, error) {
|
||||
upgrader := websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
// the connection has been "handled" -- WriteHeader was called with Upgrade,
|
||||
// so don't return an error status code; just return an error
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
cmd := exec.Command(config.Command, config.Arguments...)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
defer stdout.Close()
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
defer stdin.Close()
|
||||
|
||||
metavars, err := buildEnv(cmd.Path, r)
|
||||
if err != nil {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
|
||||
cmd.Env = metavars
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return http.StatusBadGateway, err
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go pumpStdout(conn, stdout, done)
|
||||
pumpStdin(conn, stdin)
|
||||
|
||||
stdin.Close() // close stdin to end the process
|
||||
|
||||
if err := cmd.Process.Signal(os.Interrupt); err != nil { // signal an interrupt to kill the process
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
// terminate with extreme prejudice.
|
||||
if err := cmd.Process.Signal(os.Kill); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
// not sure what we want to do here.
|
||||
// status for an "exited" process is greater
|
||||
// than 0, but isn't really an error per se.
|
||||
// just going to ignore it for now.
|
||||
cmd.Wait()
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// buildEnv creates the meta-variables for the child process according
|
||||
// to the CGI 1.1 specification: http://tools.ietf.org/html/rfc3875#section-4.1
|
||||
// cmdPath should be the path of the command being run.
|
||||
// The returned string slice can be set to the command's Env property.
|
||||
func buildEnv(cmdPath string, r *http.Request) (metavars []string, err error) {
|
||||
if !strings.Contains(r.RemoteAddr, ":") {
|
||||
r.RemoteAddr += ":"
|
||||
}
|
||||
remoteHost, remotePort, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(r.Host, ":") {
|
||||
r.Host += ":"
|
||||
}
|
||||
serverHost, serverPort, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
metavars = []string{
|
||||
`AUTH_TYPE=`, // Not used
|
||||
`CONTENT_LENGTH=`, // Not used
|
||||
`CONTENT_TYPE=`, // Not used
|
||||
`GATEWAY_INTERFACE=` + GatewayInterface,
|
||||
`PATH_INFO=`, // TODO
|
||||
`PATH_TRANSLATED=`, // TODO
|
||||
`QUERY_STRING=` + r.URL.RawQuery,
|
||||
`REMOTE_ADDR=` + remoteHost,
|
||||
`REMOTE_HOST=` + remoteHost, // Host lookups are slow - don't do them
|
||||
`REMOTE_IDENT=`, // Not used
|
||||
`REMOTE_PORT=` + remotePort,
|
||||
`REMOTE_USER=`, // Not used,
|
||||
`REQUEST_METHOD=` + r.Method,
|
||||
`REQUEST_URI=` + r.RequestURI,
|
||||
`SCRIPT_NAME=` + cmdPath, // path of the program being executed
|
||||
`SERVER_NAME=` + serverHost,
|
||||
`SERVER_PORT=` + serverPort,
|
||||
`SERVER_PROTOCOL=` + r.Proto,
|
||||
`SERVER_SOFTWARE=` + ServerSoftware,
|
||||
}
|
||||
|
||||
// Add each HTTP header to the environment as well
|
||||
for header, values := range r.Header {
|
||||
value := strings.Join(values, ", ")
|
||||
header = strings.ToUpper(header)
|
||||
header = strings.Replace(header, "-", "_", -1)
|
||||
value = strings.Replace(value, "\n", " ", -1)
|
||||
metavars = append(metavars, "HTTP_"+header+"="+value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// pumpStdin handles reading data from the websocket connection and writing
|
||||
// it to stdin of the process.
|
||||
func pumpStdin(conn *websocket.Conn, stdin io.WriteCloser) {
|
||||
// Setup our connection's websocket ping/pong handlers from our const values.
|
||||
defer conn.Close()
|
||||
conn.SetReadLimit(maxMessageSize)
|
||||
conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
conn.SetPongHandler(func(string) error { conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||
for {
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
message = append(message, '\n')
|
||||
if _, err := stdin.Write(message); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pumpStdout handles reading data from stdout of the process and writing
|
||||
// it to websocket connection.
|
||||
func pumpStdout(conn *websocket.Conn, stdout io.Reader, done chan struct{}) {
|
||||
go pinger(conn, done)
|
||||
defer func() {
|
||||
conn.Close()
|
||||
close(done) // make sure to close the pinger when we are done.
|
||||
}()
|
||||
|
||||
s := bufio.NewScanner(stdout)
|
||||
for s.Scan() {
|
||||
conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := conn.WriteMessage(websocket.TextMessage, bytes.TrimSpace(s.Bytes())); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if s.Err() != nil {
|
||||
conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, s.Err().Error()), time.Time{})
|
||||
}
|
||||
}
|
||||
|
||||
// pinger simulates the websocket to keep it alive with ping messages.
|
||||
func pinger(conn *websocket.Conn, done chan struct{}) {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer ticker.Stop()
|
||||
|
||||
for { // blocking loop with select to wait for stimulation.
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
|
||||
conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, err.Error()), time.Time{})
|
||||
return
|
||||
}
|
||||
case <-done:
|
||||
return // clean up this routine.
|
||||
}
|
||||
}
|
||||
}
|
36
vendor/github.com/mholt/caddy/caddyhttp/websocket/websocket_test.go
generated
vendored
Normal file
36
vendor/github.com/mholt/caddy/caddyhttp/websocket/websocket_test.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// 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 websocket
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildEnv(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "http://localhost", nil)
|
||||
if err != nil {
|
||||
t.Fatal("Error setting up request:", err)
|
||||
}
|
||||
req.RemoteAddr = "localhost:50302"
|
||||
|
||||
env, err := buildEnv("/bin/command", req)
|
||||
if err != nil {
|
||||
t.Fatal("Didn't expect an error:", err)
|
||||
}
|
||||
if len(env) == 0 {
|
||||
t.Fatalf("Expected non-empty environment; got %#v", env)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user