Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Łukasz Nowak
caddy
Commits
a518049f
Commit
a518049f
authored
Oct 15, 2015
by
Matthew Holt
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into configfix
parents
35e309cf
b713a779
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
437 additions
and
187 deletions
+437
-187
config/setup/basicauth_test.go
config/setup/basicauth_test.go
+1
-1
config/setup/controller.go
config/setup/controller.go
+3
-1
config/setup/errors.go
config/setup/errors.go
+2
-2
config/setup/markdown.go
config/setup/markdown.go
+2
-2
config/setup/markdown_test.go
config/setup/markdown_test.go
+6
-4
config/setup/root_test.go
config/setup/root_test.go
+13
-4
config/setup/websocket.go
config/setup/websocket.go
+8
-8
config/setup/websocket_test.go
config/setup/websocket_test.go
+7
-6
middleware/basicauth/basicauth_test.go
middleware/basicauth/basicauth_test.go
+6
-2
middleware/commands_test.go
middleware/commands_test.go
+138
-0
middleware/markdown/generator.go
middleware/markdown/generator.go
+2
-1
middleware/markdown/page.go
middleware/markdown/page.go
+1
-1
middleware/markdown/process.go
middleware/markdown/process.go
+5
-2
middleware/middleware.go
middleware/middleware.go
+9
-2
middleware/middleware_test.go
middleware/middleware_test.go
+5
-2
middleware/websocket/websocket.go
middleware/websocket/websocket.go
+229
-0
middleware/websockets/websocket.go
middleware/websockets/websocket.go
+0
-89
middleware/websockets/websockets.go
middleware/websockets/websockets.go
+0
-60
No files found.
config/setup/basicauth_test.go
View file @
a518049f
...
...
@@ -38,7 +38,7 @@ func TestBasicAuthParse(t *testing.T) {
md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
var
skipHtpassword
bool
htfh
,
err
:=
ioutil
.
TempFile
(
""
,
"basicauth-"
)
htfh
,
err
:=
ioutil
.
TempFile
(
"
.
"
,
"basicauth-"
)
if
err
!=
nil
{
t
.
Logf
(
"Error creating temp file (%v), will skip htpassword test"
,
err
)
skipHtpassword
=
true
...
...
config/setup/controller.go
View file @
a518049f
...
...
@@ -30,7 +30,9 @@ type Controller struct {
// add-ons can use this as a convenience.
func
NewTestController
(
input
string
)
*
Controller
{
return
&
Controller
{
Config
:
&
server
.
Config
{},
Config
:
&
server
.
Config
{
Root
:
"."
,
},
Dispenser
:
parse
.
NewDispenser
(
"Testfile"
,
strings
.
NewReader
(
input
)),
}
}
...
...
config/setup/errors.go
View file @
a518049f
...
...
@@ -5,7 +5,7 @@ import (
"io"
"log"
"os"
"path"
"path
/filepath
"
"strconv"
"github.com/hashicorp/go-syslog"
...
...
@@ -105,7 +105,7 @@ func errorsParse(c *Controller) (*errors.ErrorHandler, error) {
}
}
else
{
// Error page; ensure it exists
where
=
path
.
Join
(
c
.
Root
,
where
)
where
=
file
path
.
Join
(
c
.
Root
,
where
)
f
,
err
:=
os
.
Open
(
where
)
if
err
!=
nil
{
fmt
.
Println
(
"Warning: Unable to open error page '"
+
where
+
"': "
+
err
.
Error
())
...
...
config/setup/markdown.go
View file @
a518049f
...
...
@@ -114,11 +114,11 @@ func loadParams(c *Controller, mdc *markdown.Config) error {
if
_
,
ok
:=
mdc
.
Templates
[
markdown
.
DefaultTemplate
];
ok
{
return
c
.
Err
(
"only one default template is allowed, use alias."
)
}
fpath
:=
filepath
.
Clean
(
c
.
Root
+
string
(
filepath
.
Separator
)
+
tArgs
[
0
]
)
fpath
:=
filepath
.
ToSlash
(
filepath
.
Clean
(
c
.
Root
+
string
(
filepath
.
Separator
)
+
tArgs
[
0
])
)
mdc
.
Templates
[
markdown
.
DefaultTemplate
]
=
fpath
return
nil
case
2
:
fpath
:=
filepath
.
Clean
(
c
.
Root
+
string
(
filepath
.
Separator
)
+
tArgs
[
1
]
)
fpath
:=
filepath
.
ToSlash
(
filepath
.
Clean
(
c
.
Root
+
string
(
filepath
.
Separator
)
+
tArgs
[
1
])
)
mdc
.
Templates
[
tArgs
[
0
]]
=
fpath
return
nil
default
:
...
...
config/setup/markdown_test.go
View file @
a518049f
package
setup
import
(
"bytes"
"fmt"
"io/ioutil"
"net/http"
...
...
@@ -92,7 +93,7 @@ func TestMarkdownStaticGen(t *testing.T) {
t
.
Fatalf
(
"An error occured when getting the file content: %v"
,
err
)
}
expectedBody
:=
`<!DOCTYPE html>
expectedBody
:=
[]
byte
(
`<!DOCTYPE html>
<html>
<head>
<title>first_post</title>
...
...
@@ -104,9 +105,10 @@ func TestMarkdownStaticGen(t *testing.T) {
</body>
</html>
`
if
string
(
html
)
!=
expectedBody
{
t
.
Fatalf
(
"Expected file content: %v got: %v"
,
expectedBody
,
html
)
`
)
if
!
bytes
.
Equal
(
html
,
expectedBody
)
{
t
.
Fatalf
(
"Expected file content: %s got: %s"
,
string
(
expectedBody
),
string
(
html
))
}
fp
:=
filepath
.
Join
(
c
.
Root
,
markdown
.
DefaultStaticDir
)
...
...
config/setup/root_test.go
View file @
a518049f
...
...
@@ -26,9 +26,12 @@ func TestRoot(t *testing.T) {
if
err
!=
nil
{
t
.
Fatalf
(
"BeforeTest: Failed to create temp file for testing! Error was: %v"
,
err
)
}
defer
os
.
Remove
(
existingFile
.
Name
())
defer
func
()
{
existingFile
.
Close
()
os
.
Remove
(
existingFile
.
Name
())
}()
unaccessiblePath
:=
filepath
.
Join
(
existingFile
.
Name
(),
"some_name"
)
inaccessiblePath
:=
getInaccessiblePath
(
existingFile
.
Name
()
)
tests
:=
[]
struct
{
input
string
...
...
@@ -48,7 +51,7 @@ func TestRoot(t *testing.T) {
`root `
,
true
,
""
,
parseErrContent
,
},
{
fmt
.
Sprintf
(
`root %s`
,
u
naccessiblePath
),
true
,
""
,
unableToAccessErrContent
,
fmt
.
Sprintf
(
`root %s`
,
i
naccessiblePath
),
true
,
""
,
unableToAccessErrContent
,
},
{
fmt
.
Sprintf
(
`root {
...
...
@@ -60,8 +63,9 @@ func TestRoot(t *testing.T) {
for
i
,
test
:=
range
tests
{
c
:=
NewTestController
(
test
.
input
)
mid
,
err
:=
Root
(
c
)
if
test
.
shouldErr
&&
err
==
nil
{
t
.
Errorf
(
"Test %d: Expected error but found
nil for input %s"
,
i
,
test
.
input
)
t
.
Errorf
(
"Test %d: Expected error but found
%s for input %s"
,
i
,
err
,
test
.
input
)
}
if
err
!=
nil
{
...
...
@@ -97,3 +101,8 @@ func getTempDirPath() (string, error) {
return
tempDir
,
nil
}
func
getInaccessiblePath
(
file
string
)
string
{
// null byte in filename is not allowed on Windows AND unix
return
filepath
.
Join
(
"C:"
,
"file
\x00
name"
)
}
config/setup/websocket.go
View file @
a518049f
...
...
@@ -2,26 +2,26 @@ package setup
import
(
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/websocket
s
"
"github.com/mholt/caddy/middleware/websocket"
)
// WebSocket configures a new WebSocket
s
middleware instance.
// WebSocket configures a new WebSocket middleware instance.
func
WebSocket
(
c
*
Controller
)
(
middleware
.
Middleware
,
error
)
{
websocks
,
err
:=
webSocketParse
(
c
)
if
err
!=
nil
{
return
nil
,
err
}
websocket
s
.
GatewayInterface
=
c
.
AppName
+
"-CGI/1.1"
websocket
s
.
ServerSoftware
=
c
.
AppName
+
"/"
+
c
.
AppVersion
websocket
.
GatewayInterface
=
c
.
AppName
+
"-CGI/1.1"
websocket
.
ServerSoftware
=
c
.
AppName
+
"/"
+
c
.
AppVersion
return
func
(
next
middleware
.
Handler
)
middleware
.
Handler
{
return
websocket
s
.
WebSockets
{
Next
:
next
,
Sockets
:
websocks
}
return
websocket
.
WebSocket
{
Next
:
next
,
Sockets
:
websocks
}
},
nil
}
func
webSocketParse
(
c
*
Controller
)
([]
websocket
s
.
Config
,
error
)
{
var
websocks
[]
websocket
s
.
Config
func
webSocketParse
(
c
*
Controller
)
([]
websocket
.
Config
,
error
)
{
var
websocks
[]
websocket
.
Config
var
respawn
bool
optionalBlock
:=
func
()
(
hadBlock
bool
,
err
error
)
{
...
...
@@ -74,7 +74,7 @@ func webSocketParse(c *Controller) ([]websockets.Config, error) {
return
nil
,
err
}
websocks
=
append
(
websocks
,
websocket
s
.
Config
{
websocks
=
append
(
websocks
,
websocket
.
Config
{
Path
:
path
,
Command
:
cmd
,
Arguments
:
args
,
...
...
config/setup/websocket_test.go
View file @
a518049f
package
setup
import
(
"github.com/mholt/caddy/middleware/websockets"
"testing"
"github.com/mholt/caddy/middleware/websocket"
)
func
TestWebSocket
(
t
*
testing
.
T
)
{
...
...
@@ -20,10 +21,10 @@ func TestWebSocket(t *testing.T) {
}
handler
:=
mid
(
EmptyNext
)
myHandler
,
ok
:=
handler
.
(
websocket
s
.
WebSockets
)
myHandler
,
ok
:=
handler
.
(
websocket
.
WebSocket
)
if
!
ok
{
t
.
Fatalf
(
"Expected handler to be type WebSocket
s
, got: %#v"
,
handler
)
t
.
Fatalf
(
"Expected handler to be type WebSocket, got: %#v"
,
handler
)
}
if
myHandler
.
Sockets
[
0
]
.
Path
!=
"/"
{
...
...
@@ -38,15 +39,15 @@ func TestWebSocketParse(t *testing.T) {
tests
:=
[]
struct
{
inputWebSocketConfig
string
shouldErr
bool
expectedWebSocketConfig
[]
websocket
s
.
Config
expectedWebSocketConfig
[]
websocket
.
Config
}{
{
`websocket /api1 cat`
,
false
,
[]
websocket
s
.
Config
{{
{
`websocket /api1 cat`
,
false
,
[]
websocket
.
Config
{{
Path
:
"/api1"
,
Command
:
"cat"
,
}}},
{
`websocket /api3 cat
websocket /api4 cat `
,
false
,
[]
websocket
s
.
Config
{{
websocket /api4 cat `
,
false
,
[]
websocket
.
Config
{{
Path
:
"/api3"
,
Command
:
"cat"
,
},
{
...
...
middleware/basicauth/basicauth_test.go
View file @
a518049f
...
...
@@ -7,6 +7,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/mholt/caddy/middleware"
...
...
@@ -124,15 +125,18 @@ md5:$apr1$l42y8rex$pOA2VJ0x/0TwaFeAF9nX61`
t
.
Skipf
(
"Error creating temp file (%v), will skip htpassword test"
)
return
}
defer
os
.
Remove
(
htfh
.
Name
())
if
_
,
err
=
htfh
.
Write
([]
byte
(
htpasswdFile
));
err
!=
nil
{
t
.
Fatalf
(
"write htpasswd file %q: %v"
,
htfh
.
Name
(),
err
)
}
htfh
.
Close
()
defer
os
.
Remove
(
htfh
.
Name
())
for
i
,
username
:=
range
[]
string
{
"sha1"
,
"md5"
}
{
rule
:=
Rule
{
Username
:
username
,
Resources
:
[]
string
{
"/testing"
}}
if
rule
.
Password
,
err
=
GetHtpasswdMatcher
(
htfh
.
Name
(),
rule
.
Username
,
"/"
);
err
!=
nil
{
siteRoot
:=
filepath
.
Dir
(
htfh
.
Name
())
filename
:=
filepath
.
Base
(
htfh
.
Name
())
if
rule
.
Password
,
err
=
GetHtpasswdMatcher
(
filename
,
rule
.
Username
,
siteRoot
);
err
!=
nil
{
t
.
Fatalf
(
"GetHtpasswdMatcher(%q, %q): %v"
,
htfh
.
Name
(),
rule
.
Username
,
err
)
}
t
.
Logf
(
"%d. username=%q password=%v"
,
i
,
rule
.
Username
,
rule
.
Password
)
...
...
middleware/commands_test.go
0 → 100644
View file @
a518049f
package
middleware
import
(
"fmt"
"strings"
"testing"
)
func
TestSplitCommandAndArgs
(
t
*
testing
.
T
)
{
var
parseErrorContent
=
"error parsing command:"
var
noCommandErrContent
=
"no command contained in"
tests
:=
[]
struct
{
input
string
expectedCommand
string
expectedArgs
[]
string
expectedErrContent
string
}{
// Test case 0 - emtpy command
{
input
:
``
,
expectedCommand
:
``
,
expectedArgs
:
nil
,
expectedErrContent
:
noCommandErrContent
,
},
// Test case 1 - command without arguments
{
input
:
`command`
,
expectedCommand
:
`command`
,
expectedArgs
:
nil
,
expectedErrContent
:
``
,
},
// Test case 2 - command with single argument
{
input
:
`command arg1`
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`arg1`
},
expectedErrContent
:
``
,
},
// Test case 3 - command with multiple arguments
{
input
:
`command arg1 arg2`
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`arg1`
,
`arg2`
},
expectedErrContent
:
``
,
},
// Test case 4 - command with single argument with space character - in quotes
{
input
:
`command "arg1 arg1"`
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`arg1 arg1`
},
expectedErrContent
:
``
,
},
// Test case 4 - command with single argument with space character - escaped
{
input
:
`command arg1\ arg1`
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`arg1 arg1`
},
expectedErrContent
:
``
,
},
// Test case 6 - command with escaped quote character
{
input
:
`command "arg1 \" arg1"`
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`arg1 " arg1`
},
expectedErrContent
:
``
,
},
// Test case 7 - command with escaped backslash
{
input
:
`command '\arg1'`
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`\arg1`
},
expectedErrContent
:
``
,
},
// Test case 8 - command with comments
{
input
:
`command arg1 #comment1 comment2`
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`arg1`
},
expectedErrContent
:
""
,
},
// Test case 9 - command with multiple spaces and tab character
{
input
:
"command arg1 arg2
\t
arg3"
,
expectedCommand
:
`command`
,
expectedArgs
:
[]
string
{
`arg1`
,
`arg2`
,
"arg3"
},
expectedErrContent
:
""
,
},
// Test case 10 - command with unclosed quotes
{
input
:
`command "arg1 arg2`
,
expectedCommand
:
""
,
expectedArgs
:
nil
,
expectedErrContent
:
parseErrorContent
,
},
// Test case 11 - command with unclosed quotes
{
input
:
`command 'arg1 arg2"`
,
expectedCommand
:
""
,
expectedArgs
:
nil
,
expectedErrContent
:
parseErrorContent
,
},
}
for
i
,
test
:=
range
tests
{
errorPrefix
:=
fmt
.
Sprintf
(
"Test [%d]: "
,
i
)
errorSuffix
:=
fmt
.
Sprintf
(
" Command to parse: [%s]"
,
test
.
input
)
actualCommand
,
actualArgs
,
actualErr
:=
SplitCommandAndArgs
(
test
.
input
)
// test if error matches expectation
if
test
.
expectedErrContent
!=
""
{
if
actualErr
==
nil
{
t
.
Errorf
(
errorPrefix
+
"Expected error with content [%s], found no error."
+
errorSuffix
,
test
.
expectedErrContent
)
}
else
if
!
strings
.
Contains
(
actualErr
.
Error
(),
test
.
expectedErrContent
)
{
t
.
Errorf
(
errorPrefix
+
"Expected error with content [%s], found [%v]."
+
errorSuffix
,
test
.
expectedErrContent
,
actualErr
)
}
}
else
if
actualErr
!=
nil
{
t
.
Errorf
(
errorPrefix
+
"Expected no error, found [%v]."
+
errorSuffix
,
actualErr
)
}
// test if command matches
if
test
.
expectedCommand
!=
actualCommand
{
t
.
Errorf
(
"Expected command: [%s], actual: [%s]."
+
errorSuffix
,
test
.
expectedCommand
,
actualCommand
)
}
// test if arguments match
if
len
(
test
.
expectedArgs
)
!=
len
(
actualArgs
)
{
t
.
Errorf
(
"Wrong number of arguments! Expected [%v], actual [%v]."
+
errorSuffix
,
test
.
expectedArgs
,
actualArgs
)
}
for
j
,
actualArg
:=
range
actualArgs
{
expectedArg
:=
test
.
expectedArgs
[
j
]
if
actualArg
!=
expectedArg
{
t
.
Errorf
(
errorPrefix
+
"Argument at position [%d] differ! Expected [%s], actual [%s]"
+
errorSuffix
,
j
,
expectedArg
,
actualArg
)
}
}
}
}
middleware/markdown/generator.go
View file @
a518049f
...
...
@@ -70,7 +70,7 @@ func generateLinks(md Markdown, cfg *Config) (bool, error) {
return
generated
,
g
.
lastErr
}
// generateStatic
Files generates static html
files from markdowns.
// generateStatic
HTML generates static HTML
files from markdowns.
func
generateStaticHTML
(
md
Markdown
,
cfg
*
Config
)
error
{
// If generated site already exists, clear it out
_
,
err
:=
os
.
Stat
(
cfg
.
StaticDir
)
...
...
@@ -98,6 +98,7 @@ func generateStaticHTML(md Markdown, cfg *Config) error {
if
err
!=
nil
{
return
err
}
reqPath
=
filepath
.
ToSlash
(
reqPath
)
reqPath
=
"/"
+
reqPath
// Generate the static file
...
...
middleware/markdown/page.go
View file @
a518049f
...
...
@@ -116,7 +116,7 @@ func (l *linkGen) generateLinks(md Markdown, cfg *Config) bool {
if
err
!=
nil
{
return
err
}
reqPath
=
"/"
+
reqPath
reqPath
=
"/"
+
filepath
.
ToSlash
(
reqPath
)
parser
:=
findParser
(
body
)
if
parser
==
nil
{
...
...
middleware/markdown/process.go
View file @
a518049f
...
...
@@ -134,7 +134,10 @@ func (md Markdown) generatePage(c *Config, requestPath string, content []byte) e
}
}
filePath
:=
filepath
.
Join
(
c
.
StaticDir
,
requestPath
)
// the URL will always use "/" as a path separator,
// convert that to a native path to support OS that
// use different path separators
filePath
:=
filepath
.
Join
(
c
.
StaticDir
,
filepath
.
FromSlash
(
requestPath
))
// If it is index file, use the directory instead
if
md
.
IsIndexFile
(
filepath
.
Base
(
requestPath
))
{
...
...
@@ -154,7 +157,7 @@ func (md Markdown) generatePage(c *Config, requestPath string, content []byte) e
}
c
.
Lock
()
c
.
StaticFiles
[
requestPath
]
=
file
Path
c
.
StaticFiles
[
requestPath
]
=
file
path
.
ToSlash
(
filePath
)
c
.
Unlock
()
}
...
...
middleware/middleware.go
View file @
a518049f
...
...
@@ -3,7 +3,7 @@ package middleware
import
(
"net/http"
"path
/filepath
"
"path"
)
type
(
...
...
@@ -57,12 +57,19 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
// and false is returned. fpath must end in a forward slash '/'
// otherwise no index files will be tried (directory paths must end
// in a forward slash according to HTTP).
//
// All paths passed into and returned from this function use '/' as the
// path separator, just like URLs. IndexFle handles path manipulation
// internally for systems that use different path separators.
func
IndexFile
(
root
http
.
FileSystem
,
fpath
string
,
indexFiles
[]
string
)
(
string
,
bool
)
{
if
fpath
[
len
(
fpath
)
-
1
]
!=
'/'
||
root
==
nil
{
return
""
,
false
}
for
_
,
indexFile
:=
range
indexFiles
{
fp
:=
filepath
.
Join
(
fpath
,
indexFile
)
// func (http.FileSystem).Open wants all paths separated by "/",
// regardless of operating system convention, so use
// path.Join instead of filepath.Join
fp
:=
path
.
Join
(
fpath
,
indexFile
)
f
,
err
:=
root
.
Open
(
fp
)
if
err
==
nil
{
f
.
Close
()
...
...
middleware/middleware_test.go
View file @
a518049f
...
...
@@ -15,9 +15,12 @@ func TestIndexfile(t *testing.T) {
expectedBoolValue
bool
//return value
}{
{
http
.
Dir
(
"./templates/testdata"
),
"/images/"
,
[]
string
{
"img.htm"
},
http
.
Dir
(
"./templates/testdata"
),
"/images/"
,
[]
string
{
"img.htm"
},
false
,
"/images/img.htm"
,
true
,
"/images/img.htm"
,
true
,
},
}
for
i
,
test
:=
range
tests
{
...
...
middleware/websocket/websocket.go
0 → 100644
View file @
a518049f
// Package websocket implements a WebSocket server by executing
// a command and piping its input and output through the WebSocket
// connection.
package
websocket
import
(
"io"
"net"
"net/http"
"os/exec"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/mholt/caddy/middleware"
)
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
middleware
.
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
middleware
.
Path
(
r
.
URL
.
Path
)
.
Matches
(
sockconfig
.
Path
)
{
return
serveWS
(
w
,
r
,
&
sockconfig
)
}
}
// Didn't match a websocket path, so pass-thru
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
{
return
http
.
StatusBadRequest
,
err
}
defer
conn
.
Close
()
cmd
:=
exec
.
Command
(
config
.
Command
,
config
.
Arguments
...
)
stdout
,
err
:=
cmd
.
StdoutPipe
()
if
err
!=
nil
{
return
http
.
StatusBadGateway
,
err
}
stdin
,
err
:=
cmd
.
StdinPipe
()
if
err
!=
nil
{
return
http
.
StatusBadGateway
,
err
}
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
}
reader
(
conn
,
stdout
,
stdin
)
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
)
{
remoteHost
,
remotePort
,
err
:=
net
.
SplitHostPort
(
r
.
RemoteAddr
)
if
err
!=
nil
{
return
}
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
}
// reader is the guts of this package. It takes the stdin and stdout pipes
// of the cmd we created in ServeWS and pipes them between the client and server
// over websockets.
func
reader
(
conn
*
websocket
.
Conn
,
stdout
io
.
ReadCloser
,
stdin
io
.
WriteCloser
)
{
// Setup our connection's websocket ping/pong handlers from our const values.
conn
.
SetReadLimit
(
maxMessageSize
)
conn
.
SetReadDeadline
(
time
.
Now
()
.
Add
(
pongWait
))
conn
.
SetPongHandler
(
func
(
string
)
error
{
conn
.
SetReadDeadline
(
time
.
Now
()
.
Add
(
pongWait
));
return
nil
})
tickerChan
:=
make
(
chan
bool
)
defer
func
()
{
tickerChan
<-
true
}()
// make sure to close the ticker when we are done.
go
ticker
(
conn
,
tickerChan
)
for
{
msgType
,
r
,
err
:=
conn
.
NextReader
()
if
err
!=
nil
{
if
msgType
==
-
1
{
return
// we got a disconnect from the client. We are good to close.
}
conn
.
WriteControl
(
websocket
.
CloseMessage
,
websocket
.
FormatCloseMessage
(
websocket
.
CloseGoingAway
,
""
),
time
.
Time
{})
return
}
w
,
err
:=
conn
.
NextWriter
(
msgType
)
if
err
!=
nil
{
conn
.
WriteControl
(
websocket
.
CloseMessage
,
websocket
.
FormatCloseMessage
(
websocket
.
CloseGoingAway
,
""
),
time
.
Time
{})
return
}
if
_
,
err
:=
io
.
Copy
(
stdin
,
r
);
err
!=
nil
{
conn
.
WriteControl
(
websocket
.
CloseMessage
,
websocket
.
FormatCloseMessage
(
websocket
.
CloseGoingAway
,
""
),
time
.
Time
{})
return
}
go
func
()
{
if
_
,
err
:=
io
.
Copy
(
w
,
stdout
);
err
!=
nil
{
conn
.
WriteControl
(
websocket
.
CloseMessage
,
websocket
.
FormatCloseMessage
(
websocket
.
CloseGoingAway
,
""
),
time
.
Time
{})
return
}
if
err
:=
w
.
Close
();
err
!=
nil
{
conn
.
WriteControl
(
websocket
.
CloseMessage
,
websocket
.
FormatCloseMessage
(
websocket
.
CloseGoingAway
,
""
),
time
.
Time
{})
return
}
}()
}
}
// ticker is start by the reader. Basically it is the method that simulates the websocket
// between the server and client to keep it alive with ping messages.
func
ticker
(
conn
*
websocket
.
Conn
,
c
chan
bool
)
{
ticker
:=
time
.
NewTicker
(
pingPeriod
)
defer
func
()
{
ticker
.
Stop
()
close
(
c
)
}()
for
{
// blocking loop with select to wait for stimulation.
select
{
case
<-
ticker
.
C
:
conn
.
WriteMessage
(
websocket
.
PingMessage
,
nil
)
case
<-
c
:
return
// clean up this routine.
}
}
}
middleware/websockets/websocket.go
deleted
100644 → 0
View file @
35e309cf
package
websockets
import
(
"net"
"net/http"
"os/exec"
"strings"
"golang.org/x/net/websocket"
)
// WebSocket represents a web socket server instance. A WebSocket
// is instantiated for each new websocket request/connection.
type
WebSocket
struct
{
Config
*
http
.
Request
}
// Handle handles a WebSocket connection. It launches the
// specified command and streams input and output through
// the command's stdin and stdout.
func
(
ws
WebSocket
)
Handle
(
conn
*
websocket
.
Conn
)
{
cmd
:=
exec
.
Command
(
ws
.
Command
,
ws
.
Arguments
...
)
cmd
.
Stdin
=
conn
cmd
.
Stdout
=
conn
cmd
.
Stderr
=
conn
// TODO: Make this configurable from the Caddyfile
metavars
,
err
:=
ws
.
buildEnv
(
cmd
.
Path
)
if
err
!=
nil
{
panic
(
err
)
// TODO
}
cmd
.
Env
=
metavars
err
=
cmd
.
Run
()
if
err
!=
nil
{
panic
(
err
)
}
}
// 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
(
ws
WebSocket
)
buildEnv
(
cmdPath
string
)
(
metavars
[]
string
,
err
error
)
{
remoteHost
,
remotePort
,
err
:=
net
.
SplitHostPort
(
ws
.
RemoteAddr
)
if
err
!=
nil
{
return
}
serverHost
,
serverPort
,
err
:=
net
.
SplitHostPort
(
ws
.
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=`
+
ws
.
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=`
+
ws
.
Method
,
`REQUEST_URI=`
+
ws
.
RequestURI
,
`SCRIPT_NAME=`
+
cmdPath
,
// path of the program being executed
`SERVER_NAME=`
+
serverHost
,
`SERVER_PORT=`
+
serverPort
,
`SERVER_PROTOCOL=`
+
ws
.
Proto
,
`SERVER_SOFTWARE=`
+
ServerSoftware
,
}
// Add each HTTP header to the environment as well
for
header
,
values
:=
range
ws
.
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
}
middleware/websockets/websockets.go
deleted
100644 → 0
View file @
35e309cf
// Package websockets implements a WebSocket server by executing
// a command and piping its input and output through the WebSocket
// connection.
package
websockets
import
(
"net/http"
"github.com/mholt/caddy/middleware"
"golang.org/x/net/websocket"
)
type
(
// WebSockets is a type that holds configuration for the
// websocket middleware generally, like a list of all the
// websocket endpoints.
WebSockets
struct
{
// Next is the next HTTP handler in the chain for when the path doesn't match
Next
middleware
.
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
WebSockets
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
for
_
,
sockconfig
:=
range
ws
.
Sockets
{
if
middleware
.
Path
(
r
.
URL
.
Path
)
.
Matches
(
sockconfig
.
Path
)
{
socket
:=
WebSocket
{
Config
:
sockconfig
,
Request
:
r
,
}
websocket
.
Handler
(
socket
.
Handle
)
.
ServeHTTP
(
w
,
r
)
return
0
,
nil
}
}
// Didn't match a websocket path, so pass-thru
return
ws
.
Next
.
ServeHTTP
(
w
,
r
)
}
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
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment