1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
|
package libwebsocketd
import (
"errors"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gorilla/websocket"
)
var ScriptNotFoundError = errors.New("script not found")
// WebsocketdHandler is a single request information and processing structure, it handles WS requests out of all that daemon can handle (static, cgi, devconsole)
type WebsocketdHandler struct {
server *WebsocketdServer
Id string
*RemoteInfo
*URLInfo // TODO: I cannot find where it's used except in one single place as URLInfo.FilePath
Env []string
command string
}
// NewWebsocketdHandler constructs the struct and parses all required things in it...
func NewWebsocketdHandler(s *WebsocketdServer, req *http.Request, log *LogScope) (wsh *WebsocketdHandler, err error) {
wsh = &WebsocketdHandler{server: s, Id: generateId()}
log.Associate("id", wsh.Id)
wsh.RemoteInfo, err = GetRemoteInfo(req.RemoteAddr, s.Config.ReverseLookup)
if err != nil {
log.Error("session", "Could not understand remote address '%s': %s", req.RemoteAddr, err)
return nil, err
}
log.Associate("remote", wsh.RemoteInfo.Host)
wsh.URLInfo, err = GetURLInfo(req.URL.Path, s.Config)
if err != nil {
log.Access("session", "NOT FOUND: %s", err)
return nil, err
}
wsh.command = s.Config.CommandName
if s.Config.UsingScriptDir {
wsh.command = wsh.URLInfo.FilePath
}
log.Associate("command", wsh.command)
wsh.Env = createEnv(wsh, req, log)
return wsh, nil
}
func (wsh *WebsocketdHandler) accept(ws *websocket.Conn, log *LogScope) {
defer ws.Close()
log.Access("session", "CONNECT")
defer log.Access("session", "DISCONNECT")
launched, err := launchCmd(wsh.command, wsh.server.Config.CommandArgs, wsh.Env)
if err != nil {
log.Error("process", "Could not launch process %s %s (%s)", wsh.command, strings.Join(wsh.server.Config.CommandArgs, " "), err)
return
}
log.Associate("pid", strconv.Itoa(launched.cmd.Process.Pid))
binary := wsh.server.Config.Binary
process := NewProcessEndpoint(launched, binary, log)
if cms := wsh.server.Config.CloseMs; cms != 0 {
process.closetime += time.Duration(cms) * time.Millisecond
}
wsEndpoint := NewWebSocketEndpoint(ws, binary, log)
PipeEndpoints(process, wsEndpoint)
}
// RemoteInfo holds information about remote http client
type RemoteInfo struct {
Addr, Host, Port string
}
// GetRemoteInfo creates RemoteInfo structure and fills its fields appropriately
func GetRemoteInfo(remote string, doLookup bool) (*RemoteInfo, error) {
addr, port, err := net.SplitHostPort(remote)
if err != nil {
return nil, err
}
var host string
if doLookup {
hosts, err := net.LookupAddr(addr)
if err != nil || len(hosts) == 0 {
host = addr
} else {
host = hosts[0]
}
} else {
host = addr
}
return &RemoteInfo{Addr: addr, Host: host, Port: port}, nil
}
// URLInfo - structure carrying information about current request and it's mapping to filesystem
type URLInfo struct {
ScriptPath string
PathInfo string
FilePath string
}
// GetURLInfo is a function that parses path and provides URL info according to libwebsocketd.Config fields
func GetURLInfo(path string, config *Config) (*URLInfo, error) {
if !config.UsingScriptDir {
return &URLInfo{"/", path, ""}, nil
}
parts := strings.Split(path[1:], "/")
urlInfo := &URLInfo{}
for i, part := range parts {
urlInfo.ScriptPath = strings.Join([]string{urlInfo.ScriptPath, part}, "/")
urlInfo.FilePath = filepath.Join(config.ScriptDir, urlInfo.ScriptPath)
isLastPart := i == len(parts)-1
statInfo, err := os.Stat(urlInfo.FilePath)
// not a valid path
if err != nil {
return nil, ScriptNotFoundError
}
// at the end of url but is a dir
if isLastPart && statInfo.IsDir() {
return nil, ScriptNotFoundError
}
// we've hit a dir, carry on looking
if statInfo.IsDir() {
continue
}
// no extra args
if isLastPart {
return urlInfo, nil
}
// build path info from extra parts of url
urlInfo.PathInfo = "/" + strings.Join(parts[i+1:], "/")
return urlInfo, nil
}
panic(fmt.Sprintf("GetURLInfo cannot parse path %#v", path))
}
func generateId() string {
return strconv.FormatInt(time.Now().UnixNano(), 10)
}
|