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
nexedi
caddy
Commits
24fc2ae5
Commit
24fc2ae5
authored
Jan 18, 2015
by
Matthew Holt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Major refactoring; more modular middleware
parent
7b3d0056
Changes
15
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
752 additions
and
572 deletions
+752
-572
config/config.go
config/config.go
+19
-133
config/directives.go
config/directives.go
+9
-202
config/dispenser.go
config/dispenser.go
+169
-0
config/lexer.go
config/lexer.go
+18
-48
config/parser.go
config/parser.go
+98
-11
config/parsing.go
config/parsing.go
+74
-11
main.go
main.go
+6
-6
middleware/extensionless.go
middleware/extensionless.go
+15
-2
middleware/gzip.go
middleware/gzip.go
+14
-14
middleware/headers.go
middleware/headers.go
+99
-19
middleware/log.go
middleware/log.go
+44
-19
middleware/middleware.go
middleware/middleware.go
+92
-6
middleware/redirect.go
middleware/redirect.go
+55
-7
middleware/rewrite.go
middleware/rewrite.go
+27
-6
server/server.go
server/server.go
+13
-88
No files found.
config/config.go
View file @
24fc2ae5
...
@@ -2,20 +2,30 @@
...
@@ -2,20 +2,30 @@
// launching specially-configured server instances.
// launching specially-configured server instances.
package
config
package
config
import
"os"
import
(
"os"
"github.com/mholt/caddy/middleware"
)
const
(
defaultHost
=
"localhost"
defaultPort
=
"8080"
defaultRoot
=
"."
)
// Load loads a configuration file, parses it,
// Load loads a configuration file, parses it,
// and returns a slice of Config structs which
// and returns a slice of Config structs which
// can be used to create and configure server
// can be used to create and configure server
// instances.
// instances.
func
Load
(
filename
string
)
([]
Config
,
error
)
{
func
Load
(
filename
string
)
([]
Config
,
error
)
{
p
:=
parser
{}
file
,
err
:=
os
.
Open
(
filename
)
err
:=
p
.
lexer
.
Load
(
filename
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
defer
p
.
lexer
.
Close
()
defer
file
.
Close
()
return
p
.
Parse
()
p
:=
newParser
(
file
)
return
p
.
parse
()
}
}
// IsNotFound returns whether or not the error is
// IsNotFound returns whether or not the error is
...
@@ -41,21 +51,15 @@ func Default() []Config {
...
@@ -41,21 +51,15 @@ func Default() []Config {
}
}
// config represents a server configuration. It
// config represents a server configuration. It
// is populated by parsing a config file
. (Us
e
// is populated by parsing a config file
(via th
e
//
the Load function.)
//
Load function).
type
Config
struct
{
type
Config
struct
{
Host
string
Host
string
Port
string
Port
string
Root
string
Root
string
Gzip
bool
RequestLog
Log
ErrorLog
Log
Rewrites
[]
Rewrite
Redirects
[]
Redirect
Extensions
[]
string
ErrorPages
map
[
int
]
string
// Map of HTTP status code to filename
Headers
[]
Headers
TLS
TLSConfig
TLS
TLSConfig
Middleware
[]
middleware
.
Middleware
Startup
[]
func
()
error
}
}
// Address returns the host:port of c as a string.
// Address returns the host:port of c as a string.
...
@@ -63,38 +67,6 @@ func (c Config) Address() string {
...
@@ -63,38 +67,6 @@ func (c Config) Address() string {
return
c
.
Host
+
":"
+
c
.
Port
return
c
.
Host
+
":"
+
c
.
Port
}
}
// Rewrite describes an internal location rewrite.
type
Rewrite
struct
{
From
string
To
string
}
// Redirect describes an HTTP redirect.
type
Redirect
struct
{
From
string
To
string
Code
int
}
// Log represents the settings for a log.
type
Log
struct
{
Enabled
bool
OutputFile
string
Format
string
}
// Headers groups a slice of HTTP headers by a URL pattern.
type
Headers
struct
{
Url
string
Headers
[]
Header
}
// Header represents a single HTTP header, simply a name and value.
type
Header
struct
{
Name
string
Value
string
}
// TLSConfig describes how TLS should be configured and used,
// TLSConfig describes how TLS should be configured and used,
// if at all. At least a certificate and key are required.
// if at all. At least a certificate and key are required.
type
TLSConfig
struct
{
type
TLSConfig
struct
{
...
@@ -102,89 +74,3 @@ type TLSConfig struct {
...
@@ -102,89 +74,3 @@ type TLSConfig struct {
Certificate
string
Certificate
string
Key
string
Key
string
}
}
// httpRedirs is a list of supported HTTP redirect codes.
var
httpRedirs
=
map
[
string
]
int
{
"300"
:
300
,
"301"
:
301
,
"302"
:
302
,
"303"
:
303
,
"304"
:
304
,
"305"
:
305
,
"306"
:
306
,
"307"
:
307
,
"308"
:
308
,
}
// httpErrors is a list of supported HTTP error codes.
var
httpErrors
=
map
[
string
]
int
{
"400"
:
400
,
"401"
:
401
,
"402"
:
402
,
"403"
:
403
,
"404"
:
404
,
"405"
:
405
,
"406"
:
406
,
"407"
:
407
,
"408"
:
408
,
"409"
:
409
,
"410"
:
410
,
"411"
:
411
,
"412"
:
412
,
"413"
:
413
,
"414"
:
414
,
"415"
:
415
,
"416"
:
416
,
"417"
:
417
,
"418"
:
418
,
"419"
:
419
,
"420"
:
420
,
"422"
:
422
,
"423"
:
423
,
"424"
:
424
,
"426"
:
426
,
"428"
:
428
,
"429"
:
429
,
"431"
:
431
,
"440"
:
440
,
"444"
:
444
,
"449"
:
449
,
"450"
:
450
,
"451"
:
451
,
"494"
:
494
,
"495"
:
495
,
"496"
:
496
,
"497"
:
497
,
"498"
:
498
,
"499"
:
499
,
"500"
:
500
,
"501"
:
501
,
"502"
:
502
,
"503"
:
503
,
"504"
:
504
,
"505"
:
505
,
"506"
:
506
,
"507"
:
507
,
"508"
:
508
,
"509"
:
509
,
"510"
:
510
,
"511"
:
511
,
"520"
:
520
,
"521"
:
521
,
"522"
:
522
,
"523"
:
523
,
"524"
:
524
,
"598"
:
598
,
"599"
:
599
,
}
const
(
defaultHost
=
"localhost"
defaultPort
=
"8080"
defaultRoot
=
"."
)
const
(
DefaultRequestsLog
=
"requests.log"
DefaultErrorsLog
=
"errors.log"
)
config/directives.go
View file @
24fc2ae5
package
config
package
config
import
"os"
// dirFunc is a type of parsing function which processes
// dirFunc is a type of parsing function which processes
// a particular directive and populates the config.
// a particular directive and populates the config.
type
dirFunc
func
(
*
parser
)
error
type
dirFunc
func
(
*
parser
)
error
...
@@ -15,23 +17,23 @@ func init() {
...
@@ -15,23 +17,23 @@ func init() {
// invokes a method that uses this map.
// invokes a method that uses this map.
validDirectives
=
map
[
string
]
dirFunc
{
validDirectives
=
map
[
string
]
dirFunc
{
"root"
:
func
(
p
*
parser
)
error
{
"root"
:
func
(
p
*
parser
)
error
{
if
!
p
.
lexer
.
N
extArg
()
{
if
!
p
.
n
extArg
()
{
return
p
.
argErr
()
return
p
.
argErr
()
}
}
p
.
cfg
.
Root
=
p
.
tkn
()
p
.
cfg
.
Root
=
p
.
tkn
()
return
nil
return
nil
},
},
"import"
:
func
(
p
*
parser
)
error
{
"import"
:
func
(
p
*
parser
)
error
{
if
!
p
.
lexer
.
N
extArg
()
{
if
!
p
.
n
extArg
()
{
return
p
.
argErr
()
return
p
.
argErr
()
}
}
p2
:=
parser
{}
file
,
err
:=
os
.
Open
(
p
.
tkn
())
err
:=
p2
.
lexer
.
Load
(
p
.
tkn
())
if
err
!=
nil
{
if
err
!=
nil
{
return
p
.
err
(
"Parse"
,
err
.
Error
())
return
p
.
err
(
"Parse"
,
err
.
Error
())
}
}
defer
p2
.
lexer
.
Close
()
defer
file
.
Close
()
p2
:=
newParser
(
file
)
p2
.
cfg
=
p
.
cfg
p2
.
cfg
=
p
.
cfg
err
=
p2
.
directives
()
err
=
p2
.
directives
()
...
@@ -42,210 +44,15 @@ func init() {
...
@@ -42,210 +44,15 @@ func init() {
return
nil
return
nil
},
},
"gzip"
:
func
(
p
*
parser
)
error
{
p
.
cfg
.
Gzip
=
true
return
nil
},
"log"
:
func
(
p
*
parser
)
error
{
log
:=
Log
{
Enabled
:
true
}
// Get the type of log (requests, errors, etc.)
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
logWhat
:=
p
.
tkn
()
// Set the log output file
if
p
.
lexer
.
NextArg
()
{
log
.
OutputFile
=
p
.
tkn
()
}
// Set the log output format
if
p
.
lexer
.
NextArg
()
{
log
.
Format
=
p
.
tkn
()
}
switch
logWhat
{
case
"requests"
:
if
log
.
OutputFile
==
""
||
log
.
OutputFile
==
"_"
{
log
.
OutputFile
=
DefaultRequestsLog
}
p
.
cfg
.
RequestLog
=
log
case
"errors"
:
if
log
.
OutputFile
==
""
||
log
.
OutputFile
==
"_"
{
log
.
OutputFile
=
DefaultErrorsLog
}
p
.
cfg
.
ErrorLog
=
log
default
:
return
p
.
err
(
"Parse"
,
"Unknown log '"
+
logWhat
+
"'"
)
}
return
nil
},
"rewrite"
:
func
(
p
*
parser
)
error
{
var
rw
Rewrite
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
rw
.
From
=
p
.
tkn
()
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
rw
.
To
=
p
.
tkn
()
p
.
cfg
.
Rewrites
=
append
(
p
.
cfg
.
Rewrites
,
rw
)
return
nil
},
"redir"
:
func
(
p
*
parser
)
error
{
var
redir
Redirect
// From
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
redir
.
From
=
p
.
tkn
()
// To
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
redir
.
To
=
p
.
tkn
()
// Status Code
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
if
code
,
ok
:=
httpRedirs
[
p
.
tkn
()];
!
ok
{
return
p
.
err
(
"Parse"
,
"Invalid redirect code '"
+
p
.
tkn
()
+
"'"
)
}
else
{
redir
.
Code
=
code
}
p
.
cfg
.
Redirects
=
append
(
p
.
cfg
.
Redirects
,
redir
)
return
nil
},
"ext"
:
func
(
p
*
parser
)
error
{
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
p
.
cfg
.
Extensions
=
append
(
p
.
cfg
.
Extensions
,
p
.
tkn
())
for
p
.
lexer
.
NextArg
()
{
p
.
cfg
.
Extensions
=
append
(
p
.
cfg
.
Extensions
,
p
.
tkn
())
}
return
nil
},
"error"
:
func
(
p
*
parser
)
error
{
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
if
code
,
ok
:=
httpErrors
[
p
.
tkn
()];
!
ok
{
return
p
.
err
(
"Syntax"
,
"Invalid error code '"
+
p
.
tkn
()
+
"'"
)
}
else
if
val
,
exists
:=
p
.
cfg
.
ErrorPages
[
code
];
exists
{
return
p
.
err
(
"Config"
,
p
.
tkn
()
+
" error page already configured to be '"
+
val
+
"'"
)
}
else
{
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
p
.
cfg
.
ErrorPages
[
code
]
=
p
.
tkn
()
}
return
nil
},
"header"
:
func
(
p
*
parser
)
error
{
var
head
Headers
var
isNewPattern
bool
if
!
p
.
lexer
.
NextArg
()
{
return
p
.
argErr
()
}
pattern
:=
p
.
tkn
()
// See if we already have a definition for this URL pattern...
for
_
,
h
:=
range
p
.
cfg
.
Headers
{
if
h
.
Url
==
pattern
{
head
=
h
break
}
}
// ...otherwise, this is a new pattern
if
head
.
Url
==
""
{
head
.
Url
=
pattern
isNewPattern
=
true
}
processHeaderBlock
:=
func
()
error
{
err
:=
p
.
openCurlyBrace
()
if
err
!=
nil
{
return
err
}
for
p
.
lexer
.
Next
()
{
if
p
.
tkn
()
==
"}"
{
break
}
h
:=
Header
{
Name
:
p
.
tkn
()}
if
p
.
lexer
.
NextArg
()
{
h
.
Value
=
p
.
tkn
()
}
head
.
Headers
=
append
(
head
.
Headers
,
h
)
}
err
=
p
.
closeCurlyBrace
()
if
err
!=
nil
{
return
err
}
return
nil
}
// A single header could be declared on the same line, or
// multiple headers can be grouped by URL pattern, so we have
// to look for both here.
if
p
.
lexer
.
NextArg
()
{
if
p
.
tkn
()
==
"{"
{
err
:=
processHeaderBlock
()
if
err
!=
nil
{
return
err
}
}
else
{
h
:=
Header
{
Name
:
p
.
tkn
()}
if
p
.
lexer
.
NextArg
()
{
h
.
Value
=
p
.
tkn
()
}
head
.
Headers
=
append
(
head
.
Headers
,
h
)
}
}
else
{
// Okay, it might be an opening curly brace on the next line
if
!
p
.
lexer
.
Next
()
{
return
p
.
eofErr
()
}
err
:=
processHeaderBlock
()
if
err
!=
nil
{
return
err
}
}
if
isNewPattern
{
p
.
cfg
.
Headers
=
append
(
p
.
cfg
.
Headers
,
head
)
}
else
{
for
i
:=
0
;
i
<
len
(
p
.
cfg
.
Headers
);
i
++
{
if
p
.
cfg
.
Headers
[
i
]
.
Url
==
pattern
{
p
.
cfg
.
Headers
[
i
]
=
head
break
}
}
}
return
nil
},
"tls"
:
func
(
p
*
parser
)
error
{
"tls"
:
func
(
p
*
parser
)
error
{
tls
:=
TLSConfig
{
Enabled
:
true
}
tls
:=
TLSConfig
{
Enabled
:
true
}
if
!
p
.
lexer
.
N
extArg
()
{
if
!
p
.
n
extArg
()
{
return
p
.
argErr
()
return
p
.
argErr
()
}
}
tls
.
Certificate
=
p
.
tkn
()
tls
.
Certificate
=
p
.
tkn
()
if
!
p
.
lexer
.
N
extArg
()
{
if
!
p
.
n
extArg
()
{
return
p
.
argErr
()
return
p
.
argErr
()
}
}
tls
.
Key
=
p
.
tkn
()
tls
.
Key
=
p
.
tkn
()
...
...
config/dispenser.go
0 → 100644
View file @
24fc2ae5
package
config
import
(
"errors"
"fmt"
"github.com/mholt/caddy/middleware"
)
// dispenser is a type that gets exposed to middleware
// generators so that they can parse tokens to configure
// their instance.
type
dispenser
struct
{
parser
*
parser
iter
int
tokens
[]
token
err
error
}
// newDispenser returns a new dispenser.
func
newDispenser
(
p
*
parser
)
*
dispenser
{
d
:=
new
(
dispenser
)
d
.
iter
=
-
1
d
.
parser
=
p
return
d
}
// Next loads the next token. Returns true if a token
// was loaded; false otherwise. If false, all tokens
// have been consumed.
// TODO: Have the other Next functions call this one...?
func
(
d
*
dispenser
)
Next
()
bool
{
if
d
.
iter
>=
len
(
d
.
tokens
)
-
1
{
return
false
}
else
{
d
.
iter
++
return
true
}
}
// NextArg loads the next token if it is on the same
// line. Returns true if a token was loaded; false
// otherwise. If false, all tokens on the line have
// been consumed.
func
(
d
*
dispenser
)
NextArg
()
bool
{
if
d
.
iter
<
0
{
d
.
iter
++
return
true
}
if
d
.
iter
>=
len
(
d
.
tokens
)
{
return
false
}
if
d
.
iter
<
len
(
d
.
tokens
)
-
1
&&
d
.
tokens
[
d
.
iter
]
.
line
==
d
.
tokens
[
d
.
iter
+
1
]
.
line
{
d
.
iter
++
return
true
}
return
false
}
// TODO: Keep this method? It's like NextArg
// but only gets the next token if it's on the next line...
func
(
d
*
dispenser
)
NextLine
()
bool
{
if
d
.
iter
<
0
{
d
.
iter
++
return
true
}
if
d
.
iter
>=
len
(
d
.
tokens
)
{
return
false
}
if
d
.
iter
<
len
(
d
.
tokens
)
-
1
&&
d
.
tokens
[
d
.
iter
]
.
line
<
d
.
tokens
[
d
.
iter
+
1
]
.
line
{
d
.
iter
++
return
true
}
return
false
}
// OpenCurlyBrace asserts that the current token is
// an opening curly brace "{". If it isn't, an error
// is produced and false is returned.
func
(
d
*
dispenser
)
OpenCurlyBrace
()
bool
{
if
d
.
Val
()
==
"{"
{
return
true
}
else
{
d
.
Err
(
"Parse"
,
"Expected '{'"
)
return
false
}
}
// CloseCurlyBrace asserts that the current token is
// a closing curly brace "}". If it isn't, an error
// is produced and false is returned.
func
(
d
*
dispenser
)
CloseCurlyBrace
()
bool
{
if
d
.
Val
()
==
"}"
{
return
true
}
else
{
d
.
Err
(
"Parse"
,
"Expected '}'"
)
return
false
}
}
// Val gets the text of the current token.
func
(
d
*
dispenser
)
Val
()
string
{
if
d
.
iter
>=
len
(
d
.
tokens
)
||
d
.
iter
<
0
{
return
""
}
else
{
return
d
.
tokens
[
d
.
iter
]
.
text
}
}
// ArgErr generates an argument error, meaning that another
// argument was expected but not found. The error is saved
// within the dispenser, but this function returns nil for
// convenience.
func
(
d
*
dispenser
)
ArgErr
()
middleware
.
Middleware
{
if
d
.
Val
()
==
"{"
{
d
.
Err
(
"Syntax"
,
"Unexpected token '{', expecting argument for directive"
)
return
nil
}
d
.
Err
(
"Syntax"
,
"Unexpected line break after '"
+
d
.
tokens
[
d
.
iter
]
.
text
+
"' (missing arguments?)"
)
return
nil
}
// Err generates a custom error of type kind and with a message
// of msg. The kind should be capitalized. This function returns
// nil for convenience, but loads the error into the dispenser
// so it can be reported immediately.
func
(
d
*
dispenser
)
Err
(
kind
,
msg
string
)
middleware
.
Middleware
{
msg
=
fmt
.
Sprintf
(
"%s:%d - %s error: %s"
,
d
.
parser
.
filename
,
d
.
tokens
[
d
.
iter
]
.
line
,
kind
,
msg
)
d
.
err
=
errors
.
New
(
msg
)
return
nil
}
// Args is a convenience function that loads the next arguments
// (tokens on the same line) into an arbitrary number of strings
// pointed to in targets. If there are fewer tokens available
// than string pointers, the remaining strings will not be changed.
func
(
d
*
dispenser
)
Args
(
targets
...*
string
)
{
i
:=
0
for
d
.
NextArg
()
{
*
targets
[
i
]
=
d
.
Val
()
i
++
}
}
// Startup registers a function to execute when the server starts.
func
(
d
*
dispenser
)
Startup
(
fn
func
()
error
)
{
d
.
parser
.
cfg
.
Startup
=
append
(
d
.
parser
.
cfg
.
Startup
,
fn
)
}
// Root returns the server root file path.
func
(
d
*
dispenser
)
Root
()
string
{
if
d
.
parser
.
cfg
.
Root
==
""
{
return
"."
}
else
{
return
d
.
parser
.
cfg
.
Root
}
}
// Host returns the hostname the server is bound to.
func
(
d
*
dispenser
)
Host
()
string
{
return
d
.
parser
.
cfg
.
Host
}
// Port returns the port that the server is listening on.
func
(
d
*
dispenser
)
Port
()
string
{
return
d
.
parser
.
cfg
.
Port
}
config/lexer.go
View file @
24fc2ae5
...
@@ -3,58 +3,35 @@ package config
...
@@ -3,58 +3,35 @@ package config
import
(
import
(
"bufio"
"bufio"
"io"
"io"
"os"
"unicode"
"unicode"
)
)
//
L
exer is a utility which can get values, token by
//
l
exer is a utility which can get values, token by
// token, from a
config file
. A token is a word, and tokens
// token, from a
reader
. A token is a word, and tokens
// are separated by whitespace. A word can be enclosed in
// are separated by whitespace. A word can be enclosed in
// quotes if it contains whitespace.
// quotes if it contains whitespace.
type
lexer
struct
{
type
lexer
struct
{
file
*
os
.
File
reader
*
bufio
.
Reader
reader
*
bufio
.
Reader
token
token
token
token
line
int
line
int
}
}
// Load opens a file and prepares to scan the file.
// load prepares the lexer to scan a file for tokens.
func
(
l
*
lexer
)
Load
(
filename
string
)
error
{
func
(
l
*
lexer
)
load
(
file
io
.
Reader
)
error
{
f
,
err
:=
os
.
Open
(
filename
)
l
.
reader
=
bufio
.
NewReader
(
file
)
if
err
!=
nil
{
return
err
}
l
.
reader
=
bufio
.
NewReader
(
f
)
l
.
file
=
f
l
.
line
=
1
l
.
line
=
1
return
nil
return
nil
}
}
// Close closes the file.
// next loads the next token into the lexer.
func
(
l
*
lexer
)
Close
()
{
// A token is delimited by whitespace, unless
l
.
file
.
Close
()
// the token starts with a quotes character (")
}
// in which case the token goes until the closing
// quotes (the enclosing quotes are not included).
// Next gets the next token from the input. The resulting token
// The rest of the line is skipped if a "#"
// is in l.token if next returns true. If Next returns false,
// character is read in. Returns true if a token
// there are no more tokens.
// was loaded; false otherwise.
func
(
l
*
lexer
)
Next
()
bool
{
func
(
l
*
lexer
)
next
()
bool
{
return
l
.
next
(
true
)
}
// NextArg works just like Next, but returns false if the next
// token is not on the same line as the one before. This method
// makes it easier to throw syntax errors when more values are
// expected on the same line.
func
(
l
*
lexer
)
NextArg
()
bool
{
return
l
.
next
(
false
)
}
// next gets the next token according to newlineOK, which
// specifies whether it's OK if the next token is on another
// line. Returns true if there was a new token loaded, false
// otherwise.
func
(
l
*
lexer
)
next
(
newlineOK
bool
)
bool
{
var
val
[]
rune
var
val
[]
rune
var
comment
,
quoted
,
escaped
bool
var
comment
,
quoted
,
escaped
bool
...
@@ -99,21 +76,15 @@ func (l *lexer) next(newlineOK bool) bool {
...
@@ -99,21 +76,15 @@ func (l *lexer) next(newlineOK bool) bool {
}
}
if
unicode
.
IsSpace
(
ch
)
{
if
unicode
.
IsSpace
(
ch
)
{
if
ch
==
'\r'
{
continue
}
if
ch
==
'\n'
{
if
ch
==
'\n'
{
l
.
line
++
l
.
line
++
comment
=
false
comment
=
false
}
}
if
len
(
val
)
>
0
{
if
len
(
val
)
>
0
{
return
makeToken
()
return
makeToken
()
}
else
if
!
newlineOK
{
err
:=
l
.
reader
.
UnreadRune
()
if
err
!=
nil
{
panic
(
err
)
}
if
ch
==
'\n'
{
l
.
line
--
}
return
false
}
}
continue
continue
}
}
...
@@ -138,8 +109,7 @@ func (l *lexer) next(newlineOK bool) bool {
...
@@ -138,8 +109,7 @@ func (l *lexer) next(newlineOK bool) bool {
}
}
}
}
// A token represents a single valuable/processable unit
// token represents a single processable unit.
// in a config file.
type
token
struct
{
type
token
struct
{
line
int
line
int
text
string
text
string
...
...
config/parser.go
View file @
24fc2ae5
...
@@ -3,34 +3,121 @@ package config
...
@@ -3,34 +3,121 @@ package config
import
(
import
(
"errors"
"errors"
"fmt"
"fmt"
"os"
"strings"
"strings"
"github.com/mholt/caddy/middleware"
)
)
// parser is a type which can parse config files.
// parser is a type which can parse config files.
type
parser
struct
{
type
parser
struct
{
lexer
lexer
filename
string
// the name of the file that we're parsing
cfg
Config
lexer
lexer
// the lexer that is giving us tokens from the raw input
cfg
Config
// each server gets one Config; this is the one we're currently building
other
map
[
string
]
*
dispenser
// tokens to be parsed later by others (middleware generators)
unused
bool
// sometimes the token won't be immediately consumed
}
// newParser makes a new parser and prepares it for parsing, given
// the input to parse.
func
newParser
(
file
*
os
.
File
)
*
parser
{
p
:=
&
parser
{
filename
:
file
.
Name
()}
p
.
lexer
.
load
(
file
)
return
p
}
}
// Parse parses the configuration file. It produces a slice of Config
// Parse parses the configuration file. It produces a slice of Config
// structs which can be used to create and configure server instances.
// structs which can be used to create and configure server instances.
func
(
p
*
parser
)
P
arse
()
([]
Config
,
error
)
{
func
(
p
*
parser
)
p
arse
()
([]
Config
,
error
)
{
var
configs
[]
Config
var
configs
[]
Config
for
p
.
lexer
.
Next
()
{
for
p
.
lexer
.
next
()
{
p
.
cfg
=
Config
{
ErrorPages
:
make
(
map
[
int
]
string
)}
err
:=
p
.
parseOne
()
err
:=
p
.
parse
()
if
err
!=
nil
{
if
err
!=
nil
{
return
configs
,
err
return
nil
,
err
}
}
configs
=
append
(
configs
,
p
.
cfg
)
configs
=
append
(
configs
,
p
.
cfg
)
}
}
return
configs
,
nil
return
configs
,
nil
}
}
// nextArg loads the next token if it is on the same line.
// Returns true if a token was loaded; false otherwise.
func
(
p
*
parser
)
nextArg
()
bool
{
if
p
.
unused
{
return
false
}
line
:=
p
.
line
()
if
p
.
next
()
{
if
p
.
line
()
>
line
{
p
.
unused
=
true
return
false
}
return
true
}
return
false
}
// next loads the next token and returns true if a token
// was loaded; false otherwise.
func
(
p
*
parser
)
next
()
bool
{
if
p
.
unused
{
p
.
unused
=
false
return
true
}
else
{
return
p
.
lexer
.
next
()
}
}
// parseOne parses the contents of a configuration
// file for a single Config object (each server or
// virtualhost instance gets their own Config struct),
// which is until the next address/server block.
// Call this only after you know that the lexer has another
// another token and you're not in the middle of a server
// block already.
func
(
p
*
parser
)
parseOne
()
error
{
p
.
cfg
=
Config
{}
p
.
other
=
make
(
map
[
string
]
*
dispenser
)
err
:=
p
.
begin
()
if
err
!=
nil
{
return
err
}
err
=
p
.
unwrap
()
if
err
!=
nil
{
return
err
}
return
nil
}
// unwrap gets the middleware generators from the middleware
// package in the order in which they are registered, and
// executes the top-level functions (the generator function)
// to expose the second layers which is the actual middleware.
// This function should be called only after p has filled out
// p.other and that the entire server block has been consumed.
func
(
p
*
parser
)
unwrap
()
error
{
for
_
,
directive
:=
range
middleware
.
Ordered
()
{
if
disp
,
ok
:=
p
.
other
[
directive
];
ok
{
if
generator
,
ok
:=
middleware
.
GetGenerator
(
directive
);
ok
{
mid
:=
generator
(
disp
)
if
mid
==
nil
{
return
disp
.
err
}
p
.
cfg
.
Middleware
=
append
(
p
.
cfg
.
Middleware
,
mid
)
}
else
{
return
errors
.
New
(
"No middleware bound to directive '"
+
directive
+
"'"
)
}
}
}
return
nil
}
// tkn is shorthand to get the text/value of the current token.
// tkn is shorthand to get the text/value of the current token.
func
(
p
*
parser
)
tkn
()
string
{
func
(
p
*
parser
)
tkn
()
string
{
return
p
.
lexer
.
token
.
text
return
p
.
lexer
.
token
.
text
...
@@ -58,10 +145,10 @@ func (p *parser) eofErr() error {
...
@@ -58,10 +145,10 @@ func (p *parser) eofErr() error {
return
p
.
err
(
"Syntax"
,
"Unexpected EOF"
)
return
p
.
err
(
"Syntax"
,
"Unexpected EOF"
)
}
}
// err creates a
"{{kind}} error: ..." with a custom message msg
. The
// err creates a
n error with a custom message msg: "{{kind}} error: {{msg}}"
. The
// file name and line number are included in the error message.
// file name and line number are included in the error message.
func
(
p
*
parser
)
err
(
kind
,
msg
string
)
error
{
func
(
p
*
parser
)
err
(
kind
,
msg
string
)
error
{
msg
=
fmt
.
Sprintf
(
"%s
error: %s:%d - %s"
,
kind
,
p
.
lexer
.
file
.
Name
(),
p
.
line
()
,
msg
)
msg
=
fmt
.
Sprintf
(
"%s
:%d - %s error: %s"
,
p
.
filename
,
p
.
line
(),
kind
,
msg
)
return
errors
.
New
(
msg
)
return
errors
.
New
(
msg
)
}
}
...
...
config/parsing.go
View file @
24fc2ae5
package
config
package
config
import
"github.com/mholt/caddy/middleware"
// This file contains the recursive-descent parsing
// This file contains the recursive-descent parsing
// functions.
// functions.
//
parse
is the top of the recursive-descent parsing.
//
begin
is the top of the recursive-descent parsing.
// It parses at most
1
server configuration (an address
// It parses at most
one
server configuration (an address
// and its directives).
// and its directives).
func
(
p
*
parser
)
parse
()
error
{
func
(
p
*
parser
)
begin
()
error
{
err
:=
p
.
address
()
err
:=
p
.
address
()
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
...
@@ -23,15 +25,23 @@ func (p *parser) parse() error {
...
@@ -23,15 +25,23 @@ func (p *parser) parse() error {
// address expects that the current token is a host:port
// address expects that the current token is a host:port
// combination.
// combination.
func
(
p
*
parser
)
address
()
error
{
func
(
p
*
parser
)
address
()
error
{
if
p
.
tkn
()
==
"}"
||
p
.
tkn
()
==
"{"
{
return
p
.
err
(
"Syntax"
,
"'"
+
p
.
tkn
()
+
"' is not a listening address or EOF"
)
}
p
.
cfg
.
Host
,
p
.
cfg
.
Port
=
parseAddress
(
p
.
tkn
())
p
.
cfg
.
Host
,
p
.
cfg
.
Port
=
parseAddress
(
p
.
tkn
())
p
.
lexer
.
Next
()
return
nil
return
nil
}
}
// addressBlock leads into parsing directives. It
// addressBlock leads into parsing directives, including
// handles directives enclosed by curly braces and
// possible opening/closing curly braces around the block.
// It handles directives enclosed by curly braces and
// directives not enclosed by curly braces.
// directives not enclosed by curly braces.
func
(
p
*
parser
)
addressBlock
()
error
{
func
(
p
*
parser
)
addressBlock
()
error
{
if
!
p
.
next
()
{
// file consisted of only an address
return
nil
}
err
:=
p
.
openCurlyBrace
()
err
:=
p
.
openCurlyBrace
()
if
err
!=
nil
{
if
err
!=
nil
{
// meh, single-server configs don't need curly braces
// meh, single-server configs don't need curly braces
...
@@ -51,7 +61,9 @@ func (p *parser) addressBlock() error {
...
@@ -51,7 +61,9 @@ func (p *parser) addressBlock() error {
}
}
// openCurlyBrace expects the current token to be an
// openCurlyBrace expects the current token to be an
// opening curly brace.
// opening curly brace. This acts like an assertion
// because it returns an error if the token is not
// a opening curly brace.
func
(
p
*
parser
)
openCurlyBrace
()
error
{
func
(
p
*
parser
)
openCurlyBrace
()
error
{
if
p
.
tkn
()
!=
"{"
{
if
p
.
tkn
()
!=
"{"
{
return
p
.
syntaxErr
(
"{"
)
return
p
.
syntaxErr
(
"{"
)
...
@@ -60,6 +72,8 @@ func (p *parser) openCurlyBrace() error {
...
@@ -60,6 +72,8 @@ func (p *parser) openCurlyBrace() error {
}
}
// closeCurlyBrace expects the current token to be
// closeCurlyBrace expects the current token to be
// a closing curly brace. This acts like an assertion
// because it returns an error if the token is not
// a closing curly brace.
// a closing curly brace.
func
(
p
*
parser
)
closeCurlyBrace
()
error
{
func
(
p
*
parser
)
closeCurlyBrace
()
error
{
if
p
.
tkn
()
!=
"}"
{
if
p
.
tkn
()
!=
"}"
{
...
@@ -73,18 +87,67 @@ func (p *parser) closeCurlyBrace() error {
...
@@ -73,18 +87,67 @@ func (p *parser) closeCurlyBrace() error {
// directive. It goes until EOF or closing curly
// directive. It goes until EOF or closing curly
// brace.
// brace.
func
(
p
*
parser
)
directives
()
error
{
func
(
p
*
parser
)
directives
()
error
{
for
p
.
lexer
.
N
ext
()
{
for
p
.
n
ext
()
{
if
p
.
tkn
()
==
"}"
{
if
p
.
tkn
()
==
"}"
{
// end of address scope
break
break
}
}
if
fn
,
ok
:=
validDirectives
[
p
.
tkn
()];
!
ok
{
if
fn
,
ok
:=
validDirectives
[
p
.
tkn
()];
ok
{
return
p
.
syntaxErr
(
"[directive]"
)
}
else
{
err
:=
fn
(
p
)
err
:=
fn
(
p
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
}
else
if
middleware
.
Registered
(
p
.
tkn
())
{
err
:=
p
.
collectTokens
()
if
err
!=
nil
{
return
err
}
}
else
{
return
p
.
err
(
"Syntax"
,
"Unexpected token '"
+
p
.
tkn
()
+
"', expecting a valid directive"
)
}
}
}
}
return
nil
return
nil
}
}
// collectTokens consumes tokens until the directive's scope
// closes (either end of line or end of curly brace block).
func
(
p
*
parser
)
collectTokens
()
error
{
directive
:=
p
.
tkn
()
line
:=
p
.
line
()
nesting
:=
0
breakOk
:=
false
disp
:=
newDispenser
(
p
)
// Re-use a duplicate directive's dispenser from before
// (the parsing logic in the middleware generator must
// account for multiple occurrences of its directive, even
// if that means returning an error or overwriting settings)
if
existing
,
ok
:=
p
.
other
[
directive
];
ok
{
disp
=
existing
}
// The directive is appended as a relevant token
disp
.
tokens
=
append
(
disp
.
tokens
,
p
.
lexer
.
token
)
for
p
.
next
()
{
if
p
.
tkn
()
==
"{"
{
nesting
++
}
else
if
p
.
line
()
>
line
&&
nesting
==
0
{
p
.
unused
=
true
breakOk
=
true
break
}
else
if
p
.
tkn
()
==
"}"
&&
nesting
>
0
{
nesting
--
}
else
if
p
.
tkn
()
==
"}"
&&
nesting
==
0
{
return
p
.
err
(
"Syntax"
,
"Unexpected '}' because no matching open curly brace '{'"
)
}
disp
.
tokens
=
append
(
disp
.
tokens
,
p
.
lexer
.
token
)
}
if
!
breakOk
||
nesting
>
0
{
return
p
.
eofErr
()
}
p
.
other
[
directive
]
=
disp
return
nil
}
main.go
View file @
24fc2ae5
...
@@ -9,6 +9,12 @@ import (
...
@@ -9,6 +9,12 @@ import (
"github.com/mholt/caddy/server"
"github.com/mholt/caddy/server"
)
)
var
conf
string
func
init
()
{
flag
.
StringVar
(
&
conf
,
"conf"
,
server
.
DefaultConfigFile
,
"the configuration file to use"
)
}
func
main
()
{
func
main
()
{
var
wg
sync
.
WaitGroup
var
wg
sync
.
WaitGroup
...
@@ -40,9 +46,3 @@ func main() {
...
@@ -40,9 +46,3 @@ func main() {
wg
.
Wait
()
wg
.
Wait
()
}
}
func
init
()
{
flag
.
StringVar
(
&
conf
,
"conf"
,
server
.
DefaultConfigFile
,
"the configuration file to use"
)
}
var
conf
string
middleware/extensionless.go
View file @
24fc2ae5
...
@@ -10,11 +10,24 @@ import (
...
@@ -10,11 +10,24 @@ import (
// passed in as well as possible extensions to add, internally,
// passed in as well as possible extensions to add, internally,
// to paths requested. The first path+ext that matches a resource
// to paths requested. The first path+ext that matches a resource
// that exists will be used.
// that exists will be used.
func
Extensionless
(
root
string
,
extensions
[]
string
)
Middleware
{
func
Extensionless
(
p
parser
)
Middleware
{
var
extensions
[]
string
var
root
=
p
.
Root
()
// TODO: Big gotcha! Save this now before it goes away! We can't get this later during a request!
for
p
.
Next
()
{
if
!
p
.
NextArg
()
{
return
p
.
ArgErr
()
}
extensions
=
append
(
extensions
,
p
.
Val
())
for
p
.
NextArg
()
{
extensions
=
append
(
extensions
,
p
.
Val
())
}
}
resourceExists
:=
func
(
path
string
)
bool
{
resourceExists
:=
func
(
path
string
)
bool
{
_
,
err
:=
os
.
Stat
(
root
+
path
)
_
,
err
:=
os
.
Stat
(
root
+
path
)
// technically we should use os.IsNotExist(err)
// technically we should use os.IsNotExist(err)
// but we don't handle any other
error type
s anyway
// but we don't handle any other
kinds of error
s anyway
return
err
==
nil
return
err
==
nil
}
}
...
...
middleware/gzip.go
View file @
24fc2ae5
...
@@ -7,10 +7,8 @@ import (
...
@@ -7,10 +7,8 @@ import (
"strings"
"strings"
)
)
// Adapted from https://gist.github.com/the42/1956518
func
Gzip
(
p
parser
)
Middleware
{
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
// Gzip is middleware that gzip-compresses the response.
func
Gzip
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
if
!
strings
.
Contains
(
r
.
Header
.
Get
(
"Accept-Encoding"
),
"gzip"
)
{
if
!
strings
.
Contains
(
r
.
Header
.
Get
(
"Accept-Encoding"
),
"gzip"
)
{
next
(
w
,
r
)
next
(
w
,
r
)
...
@@ -22,6 +20,7 @@ func Gzip(next http.HandlerFunc) http.HandlerFunc {
...
@@ -22,6 +20,7 @@ func Gzip(next http.HandlerFunc) http.HandlerFunc {
gz
:=
gzipResponseWriter
{
Writer
:
gzipWriter
,
ResponseWriter
:
w
}
gz
:=
gzipResponseWriter
{
Writer
:
gzipWriter
,
ResponseWriter
:
w
}
next
(
gz
,
r
)
next
(
gz
,
r
)
}
}
}
}
}
// gzipResponeWriter wraps the underlying Write method
// gzipResponeWriter wraps the underlying Write method
...
@@ -36,5 +35,6 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
...
@@ -36,5 +35,6 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
if
w
.
Header
()
.
Get
(
"Content-Type"
)
==
""
{
if
w
.
Header
()
.
Get
(
"Content-Type"
)
==
""
{
w
.
Header
()
.
Set
(
"Content-Type"
,
http
.
DetectContentType
(
b
))
w
.
Header
()
.
Set
(
"Content-Type"
,
http
.
DetectContentType
(
b
))
}
}
return
w
.
Writer
.
Write
(
b
)
n
,
err
:=
w
.
Writer
.
Write
(
b
)
return
n
,
err
}
}
middleware/headers.go
View file @
24fc2ae5
package
middleware
package
middleware
import
(
import
"net/http"
"net/http"
"strings"
"github.com/mholt/caddy/config"
)
// Headers is middleware that adds headers to the responses
// Headers is middleware that adds headers to the responses
// for requests matching a certain path.
// for requests matching a certain path.
func
Headers
(
headers
[]
config
.
Headers
)
Middleware
{
func
Headers
(
p
parser
)
Middleware
{
type
(
// Header represents a single HTTP header, simply a name and value.
header
struct
{
Name
string
Value
string
}
// Headers groups a slice of HTTP headers by a URL pattern.
headers
struct
{
Url
string
Headers
[]
header
}
)
var
rules
[]
headers
for
p
.
Next
()
{
var
head
headers
var
isNewPattern
bool
if
!
p
.
NextArg
()
{
return
p
.
ArgErr
()
}
pattern
:=
p
.
Val
()
// See if we already have a definition for this URL pattern...
for
_
,
h
:=
range
rules
{
if
h
.
Url
==
pattern
{
head
=
h
break
}
}
// ...otherwise, this is a new pattern
if
head
.
Url
==
""
{
head
.
Url
=
pattern
isNewPattern
=
true
}
processHeaderBlock
:=
func
()
bool
{
if
!
p
.
OpenCurlyBrace
()
{
return
false
}
for
p
.
Next
()
{
if
p
.
Val
()
==
"}"
{
break
}
h
:=
header
{
Name
:
p
.
Val
()}
if
p
.
NextArg
()
{
h
.
Value
=
p
.
Val
()
}
head
.
Headers
=
append
(
head
.
Headers
,
h
)
}
if
!
p
.
CloseCurlyBrace
()
{
return
false
}
return
true
}
// A single header could be declared on the same line, or
// multiple headers can be grouped by URL pattern, so we have
// to look for both here.
if
p
.
NextArg
()
{
if
p
.
Val
()
==
"{"
{
if
!
processHeaderBlock
()
{
return
nil
}
}
else
{
h
:=
header
{
Name
:
p
.
Val
()}
if
p
.
NextArg
()
{
h
.
Value
=
p
.
Val
()
}
head
.
Headers
=
append
(
head
.
Headers
,
h
)
}
}
else
{
// Okay, it might be an opening curly brace on the next line
if
!
p
.
Next
()
{
return
p
.
Err
(
"Parse"
,
"Unexpected EOF"
)
}
if
!
processHeaderBlock
()
{
return
nil
}
}
if
isNewPattern
{
rules
=
append
(
rules
,
head
)
}
else
{
for
i
:=
0
;
i
<
len
(
rules
);
i
++
{
if
rules
[
i
]
.
Url
==
pattern
{
rules
[
i
]
=
head
break
}
}
}
}
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
for
_
,
rule
:=
range
header
s
{
for
_
,
rule
:=
range
rule
s
{
if
pathsMatch
(
r
.
URL
.
Path
,
rule
.
Url
)
{
if
Path
(
r
.
URL
.
Path
)
.
Matches
(
rule
.
Url
)
{
for
_
,
header
:=
range
rule
.
Headers
{
for
_
,
header
:=
range
rule
.
Headers
{
w
.
Header
()
.
Set
(
header
.
Name
,
header
.
Value
)
w
.
Header
()
.
Set
(
header
.
Name
,
header
.
Value
)
}
}
...
@@ -23,13 +113,3 @@ func Headers(headers []config.Headers) Middleware {
...
@@ -23,13 +113,3 @@ func Headers(headers []config.Headers) Middleware {
}
}
}
}
}
}
// Returns whether or not p1 and p2 are matching
// paths. This can be defined a number of ways
// and it is not for sure yet how to match URL/path
// strings. It may be a prefix match or a full
// string match, it may strip trailing slashes.
// Until the software hits 1.0, this will be in flux.
func
pathsMatch
(
p1
,
p2
string
)
bool
{
return
strings
.
HasPrefix
(
p1
,
p2
)
}
middleware/log.go
View file @
24fc2ae5
...
@@ -3,40 +3,65 @@ package middleware
...
@@ -3,40 +3,65 @@ package middleware
import
(
import
(
"log"
"log"
"net/http"
"net/http"
"os"
)
)
func
RequestLog
(
logger
*
log
.
Logger
,
format
string
)
Middleware
{
func
RequestLog
(
p
parser
)
Middleware
{
if
format
==
""
{
var
logWhat
,
outputFile
,
format
string
format
=
defaultReqLogFormat
var
logger
*
log
.
Logger
for
p
.
Next
()
{
p
.
Args
(
&
logWhat
,
&
outputFile
,
&
format
)
if
logWhat
==
""
{
return
p
.
ArgErr
()
}
}
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
if
outputFile
==
""
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
outputFile
=
defaultLogFilename
sw
:=
newResponseRecorder
(
w
)
}
next
(
sw
,
r
)
switch
format
{
rep
:=
newReplacer
(
r
,
sw
)
case
""
:
logger
.
Println
(
rep
.
replace
(
format
))
format
=
defaultReqLogFormat
case
"{common}"
:
format
=
commonLogFormat
case
"{combined}"
:
format
=
combinedLogFormat
}
}
}
}
}
// TODO.
// Open the log file for writing when the server starts
func
ErrorLog
(
logger
*
log
.
Logger
,
format
string
)
Middleware
{
p
.
Startup
(
func
()
error
{
if
format
==
""
{
var
err
error
format
=
defaultErrLogFormat
var
file
*
os
.
File
if
outputFile
==
"stdout"
{
file
=
os
.
Stdout
}
else
if
outputFile
==
"stderr"
{
file
=
os
.
Stderr
}
else
{
file
,
err
=
os
.
OpenFile
(
outputFile
,
os
.
O_RDWR
|
os
.
O_CREATE
|
os
.
O_APPEND
,
0666
)
if
err
!=
nil
{
return
err
}
}
}
logger
=
log
.
New
(
file
,
""
,
0
)
return
nil
})
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
sw
:=
newResponseRecorder
(
w
)
sw
:=
newResponseRecorder
(
w
)
next
(
sw
,
r
)
next
(
sw
,
r
)
// This is still TODO -- we need to define what constitutes an error to be logged
rep
:=
newReplacer
(
r
,
sw
)
//logger.Println("TODO"
)
logger
.
Println
(
rep
.
replace
(
format
)
)
}
}
}
}
}
}
const
(
const
(
defaultLogFilename
=
"access.log"
commonLogFormat
=
`{remote} `
+
emptyStringReplacer
+
` [{time}] "{method} {uri} {proto}" {status} {size}`
commonLogFormat
=
`{remote} `
+
emptyStringReplacer
+
` [{time}] "{method} {uri} {proto}" {status} {size}`
combinedLogFormat
=
commonLogFormat
+
` "{>Referer}" "{>User-Agent}"`
combinedLogFormat
=
commonLogFormat
+
` "{>Referer}" "{>User-Agent}"`
defaultReqLogFormat
=
commonLogFormat
defaultReqLogFormat
=
commonLogFormat
defaultErrLogFormat
=
"[TODO]"
)
)
middleware/middleware.go
View file @
24fc2ae5
...
@@ -2,10 +2,96 @@
...
@@ -2,10 +2,96 @@
// the servers to use, according to their configuration.
// the servers to use, according to their configuration.
package
middleware
package
middleware
import
"net/http"
import
(
"net/http"
"strings"
)
// Middleware is a type of function that generates a new
// This init function registers middleware. Register middleware
// layer of middleware. It is imperative that the HandlerFunc
// in the order they should be executed during a request.
// being passed in is executed by the middleware, otherwise
// Middlewares execute in an order like A-B-C-C-B-A.
// part of the stack will not be called.
func
init
()
{
type
Middleware
func
(
http
.
HandlerFunc
)
http
.
HandlerFunc
register
(
"gzip"
,
Gzip
)
register
(
"header"
,
Headers
)
register
(
"log"
,
RequestLog
)
register
(
"rewrite"
,
Rewrite
)
register
(
"redir"
,
Redirect
)
register
(
"ext"
,
Extensionless
)
}
type
(
// Generator represents the outer layer of a middleware that
// parses tokens to configure the middleware instance.
Generator
func
(
parser
)
Middleware
// Middleware is the middle layer which represents the traditional
// idea of middleware: it is passed the next HandlerFunc in the chain
// and returns the inner layer, which is the actual HandlerFunc.
Middleware
func
(
http
.
HandlerFunc
)
http
.
HandlerFunc
// parser is the type which middleware generators use to access
// tokens and other information they need to configure the instance.
parser
interface
{
Next
()
bool
NextArg
()
bool
NextLine
()
bool
Val
()
string
OpenCurlyBrace
()
bool
CloseCurlyBrace
()
bool
ArgErr
()
Middleware
Err
(
string
,
string
)
Middleware
Args
(
...*
string
)
Startup
(
func
()
error
)
Root
()
string
Host
()
string
Port
()
string
}
)
var
(
// registry stores the registered middleware:
// both the order and the directives to which they
// are bound.
registry
=
struct
{
directiveMap
map
[
string
]
Generator
order
[]
string
}{
directiveMap
:
make
(
map
[
string
]
Generator
),
}
)
// GetGenerator gets the generator function (outer layer)
// of a middleware, according to the directive passed in.
func
GetGenerator
(
directive
string
)
(
Generator
,
bool
)
{
rm
,
ok
:=
registry
.
directiveMap
[
directive
]
return
rm
,
ok
}
// register binds a middleware generator (outer function)
// to a directive. Upon each request, middleware will be
// executed in the order they are registered.
func
register
(
directive
string
,
generator
Generator
)
{
registry
.
directiveMap
[
directive
]
=
generator
registry
.
order
=
append
(
registry
.
order
,
directive
)
}
// Ordered returns the ordered list of registered directives.
func
Ordered
()
[]
string
{
return
registry
.
order
}
// Registered returns whether or not a directive is registered.
func
Registered
(
directive
string
)
bool
{
_
,
ok
:=
GetGenerator
(
directive
)
return
ok
}
// Path represents a URI path, maybe with pattern characters.
type
Path
string
// Path matching will probably not always be a direct
// comparison; this method assures that paths can be
// easily matched.
func
(
p
Path
)
Matches
(
other
string
)
bool
{
return
strings
.
HasPrefix
(
string
(
p
),
other
)
}
middleware/redirect.go
View file @
24fc2ae5
package
middleware
package
middleware
import
(
import
"net/http"
"net/http"
"github.com/mholt/caddy/config"
)
// Redirect is middleware for redirecting certain requests
// Redirect is middleware for redirecting certain requests
// to other locations.
// to other locations.
func
Redirect
(
redirs
[]
config
.
Redirect
)
Middleware
{
func
Redirect
(
p
parser
)
Middleware
{
// Redirect describes an HTTP redirect rule.
type
redirect
struct
{
From
string
To
string
Code
int
}
var
redirects
[]
redirect
for
p
.
Next
()
{
var
rule
redirect
// From
if
!
p
.
NextArg
()
{
return
p
.
ArgErr
()
}
rule
.
From
=
p
.
Val
()
// To
if
!
p
.
NextArg
()
{
return
p
.
ArgErr
()
}
rule
.
To
=
p
.
Val
()
// Status Code
if
!
p
.
NextArg
()
{
return
p
.
ArgErr
()
}
if
code
,
ok
:=
httpRedirs
[
p
.
Val
()];
!
ok
{
return
p
.
Err
(
"Parse"
,
"Invalid redirect code '"
+
p
.
Val
()
+
"'"
)
}
else
{
rule
.
Code
=
code
}
redirects
=
append
(
redirects
,
rule
)
}
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
for
_
,
rule
:=
range
redirs
{
for
_
,
rule
:=
range
redir
ect
s
{
if
r
.
URL
.
Path
==
rule
.
From
{
if
r
.
URL
.
Path
==
rule
.
From
{
http
.
Redirect
(
w
,
r
,
rule
.
To
,
rule
.
Code
)
http
.
Redirect
(
w
,
r
,
rule
.
To
,
rule
.
Code
)
break
break
...
@@ -21,3 +56,16 @@ func Redirect(redirs []config.Redirect) Middleware {
...
@@ -21,3 +56,16 @@ func Redirect(redirs []config.Redirect) Middleware {
}
}
}
}
}
}
// httpRedirs is a list of supported HTTP redirect codes.
var
httpRedirs
=
map
[
string
]
int
{
"300"
:
300
,
"301"
:
301
,
"302"
:
302
,
"303"
:
303
,
"304"
:
304
,
"305"
:
305
,
"306"
:
306
,
"307"
:
307
,
"308"
:
308
,
}
middleware/rewrite.go
View file @
24fc2ae5
package
middleware
package
middleware
import
(
import
"net/http"
"net/http"
"github.com/mholt/caddy/config"
)
// Rewrite is middleware for rewriting requests internally to
// Rewrite is middleware for rewriting requests internally to
// a different path.
// a different path.
func
Rewrite
(
rewrites
[]
config
.
Rewrite
)
Middleware
{
func
Rewrite
(
p
parser
)
Middleware
{
// Rewrite describes an internal location rewrite rule.
type
rewrite
struct
{
From
string
To
string
}
var
rewrites
[]
rewrite
for
p
.
Next
()
{
var
rule
rewrite
if
!
p
.
NextArg
()
{
return
p
.
ArgErr
()
}
rule
.
From
=
p
.
Val
()
if
!
p
.
NextArg
()
{
return
p
.
ArgErr
()
}
rule
.
To
=
p
.
Val
()
rewrites
=
append
(
rewrites
,
rule
)
}
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
next
http
.
HandlerFunc
)
http
.
HandlerFunc
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
return
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
for
_
,
rule
:=
range
rewrites
{
for
_
,
rule
:=
range
rewrites
{
...
...
server/server.go
View file @
24fc2ae5
...
@@ -4,13 +4,15 @@ import (
...
@@ -4,13 +4,15 @@ import (
"errors"
"errors"
"log"
"log"
"net/http"
"net/http"
"os"
"github.com/mholt/caddy/config"
"github.com/mholt/caddy/config"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware"
)
)
// servers maintains a registry of running servers.
// The default configuration file to load if none is specified
const
DefaultConfigFile
=
"Caddyfile"
// servers maintains a registry of running servers, keyed by address.
var
servers
=
make
(
map
[
string
]
*
Server
)
var
servers
=
make
(
map
[
string
]
*
Server
)
// Server represents an instance of a server, which serves
// Server represents an instance of a server, which serves
...
@@ -46,7 +48,7 @@ func New(conf config.Config) (*Server, error) {
...
@@ -46,7 +48,7 @@ func New(conf config.Config) (*Server, error) {
// Serve starts the server. It blocks until the server quits.
// Serve starts the server. It blocks until the server quits.
func
(
s
*
Server
)
Serve
()
error
{
func
(
s
*
Server
)
Serve
()
error
{
err
:=
s
.
configure
Stack
()
err
:=
s
.
build
Stack
()
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
@@ -73,73 +75,20 @@ func (s *Server) Log(v ...interface{}) {
...
@@ -73,73 +75,20 @@ func (s *Server) Log(v ...interface{}) {
}
}
}
}
//
configure
Stack builds the server's middleware stack based
//
build
Stack builds the server's middleware stack based
// on its config. This method should be called last before
// on its config. This method should be called last before
// ListenAndServe begins.
// ListenAndServe begins.
func
(
s
*
Server
)
configureStack
()
error
{
func
(
s
*
Server
)
buildStack
()
error
{
var
mid
[]
middleware
.
Middleware
s
.
fileServer
=
http
.
FileServer
(
http
.
Dir
(
s
.
config
.
Root
))
var
err
error
conf
:=
s
.
config
// FileServer is the main application layer
s
.
fileServer
=
http
.
FileServer
(
http
.
Dir
(
conf
.
Root
))
// push prepends each middleware to the stack so the
// compilation can iterate them in a natural, increasing order
push
:=
func
(
m
middleware
.
Middleware
)
{
mid
=
append
(
mid
,
nil
)
copy
(
mid
[
1
:
],
mid
[
0
:
])
mid
[
0
]
=
m
}
// BEGIN ADDING MIDDLEWARE
// Middleware will be executed in the order they're added.
if
conf
.
RequestLog
.
Enabled
{
if
conf
.
RequestLog
.
Enabled
{
s
.
reqlog
,
err
=
enableLogging
(
conf
.
RequestLog
)
if
err
!=
nil
{
return
err
}
}
push
(
middleware
.
RequestLog
(
s
.
reqlog
,
conf
.
RequestLog
.
Format
))
}
if
conf
.
ErrorLog
.
Enabled
{
for
_
,
start
:=
range
s
.
config
.
Startup
{
if
conf
.
ErrorLog
.
Enabled
{
err
:=
start
()
s
.
errlog
,
err
=
enableLogging
(
conf
.
ErrorLog
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
}
}
push
(
middleware
.
ErrorLog
(
s
.
errlog
,
conf
.
ErrorLog
.
Format
))
}
if
len
(
conf
.
Rewrites
)
>
0
{
push
(
middleware
.
Rewrite
(
conf
.
Rewrites
))
}
if
len
(
conf
.
Redirects
)
>
0
{
push
(
middleware
.
Redirect
(
conf
.
Redirects
))
}
if
len
(
conf
.
Extensions
)
>
0
{
s
.
compile
(
s
.
config
.
Middleware
)
push
(
middleware
.
Extensionless
(
conf
.
Root
,
conf
.
Extensions
))
}
if
len
(
conf
.
Headers
)
>
0
{
push
(
middleware
.
Headers
(
conf
.
Headers
))
}
if
conf
.
Gzip
{
push
(
middleware
.
Gzip
)
}
// END ADDING MIDDLEWARE
// Compiling the middleware unwraps each HandlerFunc,
// fully configured, ready to serve every request.
s
.
compile
(
mid
)
return
nil
return
nil
}
}
...
@@ -152,27 +101,3 @@ func (s *Server) compile(layers []middleware.Middleware) {
...
@@ -152,27 +101,3 @@ func (s *Server) compile(layers []middleware.Middleware) {
s
.
stack
=
layer
(
s
.
stack
)
s
.
stack
=
layer
(
s
.
stack
)
}
}
}
}
// enableLogging opens a log file and keeps it open for the lifetime
// of the server. In fact, the log file is never closed as long as
// the program is running, since the server will be running for
// that long. If that ever changes, the log file should be closed.
func
enableLogging
(
l
config
.
Log
)
(
*
log
.
Logger
,
error
)
{
var
file
*
os
.
File
var
err
error
if
l
.
OutputFile
==
"stdout"
{
file
=
os
.
Stdout
}
else
if
l
.
OutputFile
==
"stderr"
{
file
=
os
.
Stderr
}
else
{
file
,
err
=
os
.
OpenFile
(
l
.
OutputFile
,
os
.
O_RDWR
|
os
.
O_CREATE
|
os
.
O_APPEND
,
0666
)
if
err
!=
nil
{
return
nil
,
err
}
}
return
log
.
New
(
file
,
""
,
0
),
nil
}
const
DefaultConfigFile
=
"Caddyfile"
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