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
c431a07a
Commit
c431a07a
authored
Apr 30, 2016
by
Tobias Weingartner
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into md_changes
parents
dd4de698
e2234497
Changes
27
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
784 additions
and
375 deletions
+784
-375
.gitattributes
.gitattributes
+13
-6
CONTRIBUTING.md
CONTRIBUTING.md
+37
-15
ISSUE_TEMPLATE
ISSUE_TEMPLATE
+4
-0
build.bash
build.bash
+1
-0
caddy/https/crypto_test.go
caddy/https/crypto_test.go
+15
-6
caddy/https/maintain.go
caddy/https/maintain.go
+11
-2
caddy/restart.go
caddy/restart.go
+2
-2
caddy/setup/browse.go
caddy/setup/browse.go
+73
-36
caddy/setup/ext.go
caddy/setup/ext.go
+2
-1
caddy/setup/headers_test.go
caddy/setup/headers_test.go
+3
-3
dist/CHANGES.txt
dist/CHANGES.txt
+13
-5
dist/README.txt
dist/README.txt
+1
-1
middleware/browse/browse.go
middleware/browse/browse.go
+199
-136
middleware/browse/browse_test.go
middleware/browse/browse_test.go
+9
-16
middleware/context.go
middleware/context.go
+1
-1
middleware/fastcgi/fcgiclient_test.go
middleware/fastcgi/fcgiclient_test.go
+1
-1
middleware/fileserver.go
middleware/fileserver.go
+10
-8
middleware/fileserver_test.go
middleware/fileserver_test.go
+32
-15
middleware/headers/headers.go
middleware/headers/headers.go
+2
-1
middleware/headers/headers_test.go
middleware/headers/headers_test.go
+7
-0
middleware/proxy/proxy.go
middleware/proxy/proxy.go
+149
-57
middleware/proxy/proxy_test.go
middleware/proxy/proxy_test.go
+136
-1
middleware/proxy/reverseproxy.go
middleware/proxy/reverseproxy.go
+5
-40
middleware/proxy/upstream.go
middleware/proxy/upstream.go
+28
-17
middleware/replacer.go
middleware/replacer.go
+8
-0
middleware/replacer_test.go
middleware/replacer_test.go
+9
-0
server/server.go
server/server.go
+13
-5
No files found.
.gitattributes
View file @
c431a07a
*.bash text eol=lf whitespace=blank-at-eol,space-before-tab,tab-in-indent,trailing-space,tabwidth=4
# shell scripts should not use tabs to indent!
*.sh text eol=lf whitespace=blank-at-eol,space-before-tab,tab-in-indent,trailing-space,tabwidth=4
*.bash text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.sh text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
# files for systemd
# files for systemd (shell-similar)
*.path text eol=lf whitespace=blank-at-eol,space-before-tab,tab-in-indent,trailing-space,tabwidth=4
*.path text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.service text eol=lf whitespace=blank-at-eol,space-before-tab,tab-in-indent,trailing-space,tabwidth=4
*.service text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
*.timer text eol=lf whitespace=blank-at-eol,space-before-tab,tab-in-indent,trailing-space,tabwidth=4
*.timer text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
# go fmt will enforce this, but in case a user has not called "go fmt" allow GIT to catch this:
*.go text eol=lf core.whitespace whitespace=indent-with-non-tab,trailing-space,tabwidth=4
*.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
.git* text eol=auto core.whitespace whitespace=trailing-space
CONTRIBUTING.md
View file @
c431a07a
...
@@ -20,10 +20,10 @@ anything about Web development
...
@@ -20,10 +20,10 @@ anything about Web development
### Bug reports
### Bug reports
First, p
lease
[
search this repository
](
https://github.com/mholt/caddy/search?q=&type=Issues&utf8=%E2%9C%93
)
P
lease
[
search this repository
](
https://github.com/mholt/caddy/search?q=&type=Issues&utf8=%E2%9C%93
)
with a variety of keywords to ensure your bug is not already reported.
with a variety of keywords to ensure your bug is not already reported.
If
not
,
[
open an issue
](
https://github.com/mholt/caddy/issues
)
and answer the
If
unique
,
[
open an issue
](
https://github.com/mholt/caddy/issues
)
and answer the
questions so we can understand and reproduce the problematic behavior.
questions so we can understand and reproduce the problematic behavior.
The burden is on you to convince us that it is actually a bug in Caddy. This is
The burden is on you to convince us that it is actually a bug in Caddy. This is
...
@@ -39,12 +39,16 @@ getting free help. If we helped you, please consider
...
@@ -39,12 +39,16 @@ getting free help. If we helped you, please consider
### Minor improvements and new tests
### Minor improvements and new tests
Submit
[
pull requests
](
https://github.com/mholt/caddy/pulls
)
at any time
. Make
Submit
[
pull requests
](
https://github.com/mholt/caddy/pulls
)
at any time
for
sure to write tests to assert your change is working properly and is thoroughly
minor changes or new tests. Make sure to write tests to assert your change is
covered. We'll ask most pull requests to be
working properly and is thoroughly
covered. We'll ask most pull requests to be
[
squashed
](
http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html
)
,
[
squashed
](
http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html
)
,
especially with small commits.
especially with small commits.
Your pull request may be thoroughly reviewed. This is because if we accept the
PR, we also assume responsibility for it, although we would prefer you to
help maintain your code after it gets merged.
### Proposals, suggestions, ideas, new features
### Proposals, suggestions, ideas, new features
...
@@ -54,17 +58,23 @@ with a variety of keywords to ensure your suggestion/proposal is new.
...
@@ -54,17 +58,23 @@ with a variety of keywords to ensure your suggestion/proposal is new.
If so, you may open either an issue or a pull request for discussion and
If so, you may open either an issue or a pull request for discussion and
feedback.
feedback.
The advantage of issues is that you don't have to spend time actually
The advantage of issues is that you don't have to spend time implementing your
implementing your idea, but you should still describe it thoroughly. The
idea, but you should still describe it thoroughly as if someone reading it would
advantage of a pull request is that we can immediately see the impact the change
implement the whole thing starting from scratch.
will have on the project, what the code will look like, and how to improve it.
The disadvantage of pull requests is that they are unlikely to get accepted
The advantage of pull requests is that we can immediately see the impact the
without significant changes, or it may be rejected entirely. Don't worry, that
change will have on the project, what the code will look like, and how to
won't happen without an open discussion first.
improve it. The disadvantage of pull requests is that they are unlikely to get
accepted without significant changes first, or it may be rejected entirely.
Don't worry, that won't happen without an open discussion first.
If you are going to spend significant time implementing code for a pull request,
If you are going to spend significant time writing code for a new pull request,
best to open an issue first and "claim" it and get feedback before you invest
best to open an issue to "claim" it and get feedback before you invest a lot of
a lot of time.
time.
Remember: pull requests should always be thoroughly documented both via godoc
and with at least a rough draft of documentation that might go on the website
for users to read.
### Collaborator status
### Collaborator status
...
@@ -75,6 +85,18 @@ push to the repository and merge other pull requests. We hope that you will
...
@@ -75,6 +85,18 @@ push to the repository and merge other pull requests. We hope that you will
stay involved by reviewing pull requests, submitting more of your own, and
stay involved by reviewing pull requests, submitting more of your own, and
resolving issues as you are able to. Thanks for making Caddy amazing!
resolving issues as you are able to. Thanks for making Caddy amazing!
We ask that collaborators will conduct thorough code reviews and be nice to
new contributors. Before merging a PR, it's best to get the approval of
at least one or two other collaborators and/or the project owner. We prefer
squashed commits instead of many little, semantically-unimportant commits. Also,
CI and other post-commit hooks must pass before being merged except in certain
unusual circumstances.
Collaborator status may be removed for inactive users from time to time as
we see fit; this is not an insult, just a basic security precaution in case
the account becomes inactive or abandoned. Privileges can always be restored
later.
### Vulnerabilities
### Vulnerabilities
...
...
ISSUE_TEMPLATE
View file @
c431a07a
...
@@ -18,3 +18,7 @@
...
@@ -18,3 +18,7 @@
#### 6. What did you see instead (give full error messages and/or log)?
#### 6. What did you see instead (give full error messages and/or log)?
#### 7. How can someone who is starting from scratch reproduce this behavior as minimally as possible?
build.bash
View file @
c431a07a
...
@@ -17,6 +17,7 @@ set -euo pipefail
...
@@ -17,6 +17,7 @@ set -euo pipefail
:
${
output_filename
:
=
"ecaddy"
}
:
${
output_filename
:
=
"ecaddy"
}
:
${
git_repo
:
=
"
${
2
:-}
"
}
:
${
git_repo
:
=
"
${
2
:-}
"
}
:
${
git_repo
:
=
"."
}
pkg
=
main
pkg
=
main
ldflags
=()
ldflags
=()
...
...
caddy/https/crypto_test.go
View file @
c431a07a
...
@@ -8,6 +8,7 @@ import (
...
@@ -8,6 +8,7 @@ import (
"crypto/rand"
"crypto/rand"
"crypto/rsa"
"crypto/rsa"
"crypto/x509"
"crypto/x509"
"errors"
"os"
"os"
"runtime"
"runtime"
"testing"
"testing"
...
@@ -95,17 +96,25 @@ func TestSaveAndLoadECCPrivateKey(t *testing.T) {
...
@@ -95,17 +96,25 @@ func TestSaveAndLoadECCPrivateKey(t *testing.T) {
// PrivateKeysSame compares the bytes of a and b and returns true if they are the same.
// PrivateKeysSame compares the bytes of a and b and returns true if they are the same.
func
PrivateKeysSame
(
a
,
b
crypto
.
PrivateKey
)
bool
{
func
PrivateKeysSame
(
a
,
b
crypto
.
PrivateKey
)
bool
{
return
bytes
.
Equal
(
PrivateKeyBytes
(
a
),
PrivateKeyBytes
(
b
))
var
abytes
,
bbytes
[]
byte
var
err
error
if
abytes
,
err
=
PrivateKeyBytes
(
a
);
err
!=
nil
{
return
false
}
if
bbytes
,
err
=
PrivateKeyBytes
(
b
);
err
!=
nil
{
return
false
}
return
bytes
.
Equal
(
abytes
,
bbytes
)
}
}
// PrivateKeyBytes returns the bytes of DER-encoded key.
// PrivateKeyBytes returns the bytes of DER-encoded key.
func
PrivateKeyBytes
(
key
crypto
.
PrivateKey
)
[]
byte
{
func
PrivateKeyBytes
(
key
crypto
.
PrivateKey
)
([]
byte
,
error
)
{
var
keyBytes
[]
byte
switch
key
:=
key
.
(
type
)
{
switch
key
:=
key
.
(
type
)
{
case
*
rsa
.
PrivateKey
:
case
*
rsa
.
PrivateKey
:
keyBytes
=
x509
.
MarshalPKCS1PrivateKey
(
key
)
return
x509
.
MarshalPKCS1PrivateKey
(
key
),
nil
case
*
ecdsa
.
PrivateKey
:
case
*
ecdsa
.
PrivateKey
:
keyBytes
,
_
=
x509
.
MarshalECPrivateKey
(
key
)
return
x509
.
MarshalECPrivateKey
(
key
)
}
}
return
keyBytes
return
nil
,
errors
.
New
(
"Unknown private key type"
)
}
}
caddy/https/maintain.go
View file @
c431a07a
...
@@ -112,12 +112,21 @@ func renewManagedCertificates(allowPrompts bool) (err error) {
...
@@ -112,12 +112,21 @@ func renewManagedCertificates(allowPrompts bool) (err error) {
// Apply changes to the cache
// Apply changes to the cache
for
_
,
cert
:=
range
renewed
{
for
_
,
cert
:=
range
renewed
{
if
cert
.
Names
[
len
(
cert
.
Names
)
-
1
]
==
""
{
// Special case: This is the default certificate, so we must
// ensure it gets updated as well, otherwise the renewal
// routine will find it and think it still needs to be renewed,
// even though we already renewed it...
certCacheMu
.
Lock
()
delete
(
certCache
,
""
)
certCacheMu
.
Unlock
()
}
_
,
err
:=
cacheManagedCertificate
(
cert
.
Names
[
0
],
cert
.
OnDemand
)
_
,
err
:=
cacheManagedCertificate
(
cert
.
Names
[
0
],
cert
.
OnDemand
)
if
err
!=
nil
{
if
err
!=
nil
{
if
client
.
AllowPrompts
{
if
client
.
AllowPrompts
{
return
err
// operator is present, so report error immediately
return
err
// operator is present, so report error immediately
}
}
log
.
Printf
(
"[ERROR] %v"
,
err
)
log
.
Printf
(
"[ERROR]
Caching renewed certificate:
%v"
,
err
)
}
}
}
}
for
_
,
cert
:=
range
deleted
{
for
_
,
cert
:=
range
deleted
{
...
@@ -178,7 +187,7 @@ func updateOCSPStaples() {
...
@@ -178,7 +187,7 @@ func updateOCSPStaples() {
if
err
!=
nil
{
if
err
!=
nil
{
if
cert
.
OCSP
!=
nil
{
if
cert
.
OCSP
!=
nil
{
// if it was no staple before, that's fine, otherwise we should log the error
// if it was no staple before, that's fine, otherwise we should log the error
log
.
Printf
(
"[ERROR] Checking OCSP for %
s: %v"
,
name
,
err
)
log
.
Printf
(
"[ERROR] Checking OCSP for %
v: %v"
,
cert
.
Names
,
err
)
}
}
continue
continue
}
}
...
...
caddy/restart.go
View file @
c431a07a
...
@@ -11,7 +11,7 @@ import (
...
@@ -11,7 +11,7 @@ import (
"net"
"net"
"os"
"os"
"os/exec"
"os/exec"
"path"
"path
/filepath
"
"sync/atomic"
"sync/atomic"
"github.com/mholt/caddy/caddy/https"
"github.com/mholt/caddy/caddy/https"
...
@@ -138,7 +138,7 @@ func Restart(newCaddyfile Input) error {
...
@@ -138,7 +138,7 @@ func Restart(newCaddyfile Input) error {
func
getCertsForNewCaddyfile
(
newCaddyfile
Input
)
error
{
func
getCertsForNewCaddyfile
(
newCaddyfile
Input
)
error
{
// parse the new caddyfile only up to (and including) TLS
// parse the new caddyfile only up to (and including) TLS
// so we can know what we need to get certs for.
// so we can know what we need to get certs for.
configs
,
_
,
_
,
err
:=
loadConfigsUpToIncludingTLS
(
path
.
Base
(
newCaddyfile
.
Path
()),
bytes
.
NewReader
(
newCaddyfile
.
Body
()))
configs
,
_
,
_
,
err
:=
loadConfigsUpToIncludingTLS
(
file
path
.
Base
(
newCaddyfile
.
Path
()),
bytes
.
NewReader
(
newCaddyfile
.
Body
()))
if
err
!=
nil
{
if
err
!=
nil
{
return
errors
.
New
(
"loading Caddyfile: "
+
err
.
Error
())
return
errors
.
New
(
"loading Caddyfile: "
+
err
.
Error
())
}
}
...
...
caddy/setup/browse.go
View file @
c431a07a
...
@@ -3,6 +3,7 @@ package setup
...
@@ -3,6 +3,7 @@ package setup
import
(
import
(
"fmt"
"fmt"
"io/ioutil"
"io/ioutil"
"net/http"
"text/template"
"text/template"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware"
...
@@ -17,7 +18,6 @@ func Browse(c *Controller) (middleware.Middleware, error) {
...
@@ -17,7 +18,6 @@ func Browse(c *Controller) (middleware.Middleware, error) {
}
}
browse
:=
browse
.
Browse
{
browse
:=
browse
.
Browse
{
Root
:
c
.
Root
,
Configs
:
configs
,
Configs
:
configs
,
IgnoreIndexes
:
false
,
IgnoreIndexes
:
false
,
}
}
...
@@ -50,6 +50,16 @@ func browseParse(c *Controller) ([]browse.Config, error) {
...
@@ -50,6 +50,16 @@ func browseParse(c *Controller) ([]browse.Config, error) {
}
else
{
}
else
{
bc
.
PathScope
=
"/"
bc
.
PathScope
=
"/"
}
}
bc
.
Root
=
http
.
Dir
(
c
.
Root
)
theRoot
,
err
:=
bc
.
Root
.
Open
(
"/"
)
// catch a missing path early
if
err
!=
nil
{
return
configs
,
err
}
defer
theRoot
.
Close
()
_
,
err
=
theRoot
.
Readdir
(
-
1
)
if
err
!=
nil
{
return
configs
,
err
}
// Second argument would be the template file to use
// Second argument would be the template file to use
var
tplText
string
var
tplText
string
...
@@ -85,7 +95,6 @@ const defaultTemplate = `<!DOCTYPE html>
...
@@ -85,7 +95,6 @@ const defaultTemplate = `<!DOCTYPE html>
<html>
<html>
<head>
<head>
<title>{{.Name}}</title>
<title>{{.Name}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
<style>
* { padding: 0; margin: 0; }
* { padding: 0; margin: 0; }
...
@@ -106,7 +115,7 @@ h1 a:hover {
...
@@ -106,7 +115,7 @@ h1 a:hover {
}
}
header,
header,
.content
{
#summary
{
padding-left: 5%;
padding-left: 5%;
padding-right: 5%;
padding-right: 5%;
}
}
...
@@ -306,43 +315,49 @@ footer {
...
@@ -306,43 +315,49 @@ footer {
</header>
</header>
<main>
<main>
<div class="meta">
<div class="meta">
<div
class="content
">
<div
id="summary
">
<span class="meta-item"><b>{{.NumDirs}}</b> director{{if eq 1 .NumDirs}}y{{else}}ies{{end}}</span>
<span class="meta-item"><b>{{.NumDirs}}</b> director{{if eq 1 .NumDirs}}y{{else}}ies{{end}}</span>
<span class="meta-item"><b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}</span>
<span class="meta-item"><b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}</span>
{{- if ne 0 .ItemsLimitedTo}}
<span class="meta-item">(of which only <b>{{.ItemsLimitedTo}}</b> are displayed)</span>
{{- end}}
</div>
</div>
</div>
</div>
<div class="listing">
<div class="listing">
<table>
<table aria-describedby="summary">
<thead>
<tr>
<tr>
<th>
<th>
{{if and (eq .Sort "name") (ne .Order "desc")}}
{{
-
if and (eq .Sort "name") (ne .Order "desc")}}
<a href="?sort=name&order=desc">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
<a href="?sort=name&order=desc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
{{else if and (eq .Sort "name") (ne .Order "asc")}}
{{
-
else if and (eq .Sort "name") (ne .Order "asc")}}
<a href="?sort=name&order=asc">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
<a href="?sort=name&order=asc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
{{else}}
{{
-
else}}
<a href="?sort=name&order=asc">Name</a>
<a href="?sort=name&order=asc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Name</a>
{{end}}
{{
-
end}}
</th>
</th>
<th>
<th>
{{if and (eq .Sort "size") (ne .Order "desc")}}
{{
-
if and (eq .Sort "size") (ne .Order "desc")}}
<a href="?sort=size&order=desc">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a>
<a href="?sort=size&order=desc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a>
{{else if and (eq .Sort "size") (ne .Order "asc")}}
{{
-
else if and (eq .Sort "size") (ne .Order "asc")}}
<a href="?sort=size&order=asc">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a>
<a href="?sort=size&order=asc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a>
{{else}}
{{
-
else}}
<a href="?sort=size&order=asc">Size</a>
<a href="?sort=size&order=asc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Size</a>
{{end}}
{{
-
end}}
</th>
</th>
<th class="hideable">
<th class="hideable">
{{if and (eq .Sort "time") (ne .Order "desc")}}
{{
-
if and (eq .Sort "time") (ne .Order "desc")}}
<a href="?sort=time&order=desc">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a>
<a href="?sort=time&order=desc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a></a>
{{else if and (eq .Sort "time") (ne .Order "asc")}}
{{
-
else if and (eq .Sort "time") (ne .Order "asc")}}
<a href="?sort=time&order=asc">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a>
<a href="?sort=time&order=asc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a></a>
{{else}}
{{
-
else}}
<a href="?sort=time&order=asc">Modified</a>
<a href="?sort=time&order=asc
{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}
">Modified</a>
{{end}}
{{
-
end}}
</th>
</th>
</tr>
</tr>
{{if .CanGoUp}}
</thead>
<tbody>
{{- if .CanGoUp}}
<tr>
<tr>
<td>
<td>
<a href="..">
<a href="..">
...
@@ -350,30 +365,52 @@ footer {
...
@@ -350,30 +365,52 @@ footer {
</a>
</a>
</td>
</td>
<td>—</td>
<td>—</td>
<td>—</td>
<td
class="hideable"
>—</td>
</tr>
</tr>
{{end}}
{{
-
end}}
{{range .Items}}
{{
-
range .Items}}
<tr>
<tr>
<td>
<td>
<a href="{{.URL}}">
<a href="{{.URL}}">
{{if .IsDir}}
{{
-
if .IsDir}}
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 35.678803 28.527945"><use xlink:href="#folder"></use></svg>
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 35.678803 28.527945"><use xlink:href="#folder"></use></svg>
{{else}}
{{
-
else}}
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 26.604381 29.144726"><use xlink:href="#file"></use></svg>
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 26.604381 29.144726"><use xlink:href="#file"></use></svg>
{{end}}
{{
-
end}}
<span class="name">{{.Name}}</span>
<span class="name">{{.Name}}</span>
</a>
</a>
</td>
</td>
<td>{{.HumanSize}}</td>
{{- if .IsDir}}
<td class="hideable">{{.HumanModTime "01/02/2006 03:04:05 PM"}}</td>
<td data-order="-1">—</td>
{{- else}}
<td data-order="{{.Size}}">{{.HumanSize}}</td>
{{- end}}
<td class="hideable"><time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "01/02/2006 03:04:05 PM -07:00"}}</time></td>
</tr>
</tr>
{{end}}
{{- end}}
</tbody>
</table>
</table>
</div>
</div>
</main>
</main>
<footer>
<footer>
Served with <a href="https://caddyserver.com">Caddy</a>
Served with <a href="https://caddyserver.com">Caddy</a>
</footer>
</footer>
<script type="text/javascript">
function localizeDatetime(e, index, ar) {
if (e.textContent === undefined) {
return;
}
var d = new Date(e.getAttribute('datetime'));
if (isNaN(d)) {
d = new Date(e.textContent);
if (isNaN(d)) {
return;
}
}
e.textContent = d.toLocaleString();
}
var timeList = Array.prototype.slice.call(document.getElementsByTagName("time"));
timeList.forEach(localizeDatetime);
</script>
</body>
</body>
</html>`
</html>`
caddy/setup/ext.go
View file @
c431a07a
...
@@ -2,6 +2,7 @@ package setup
...
@@ -2,6 +2,7 @@ package setup
import
(
import
(
"os"
"os"
"path/filepath"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/extensions"
"github.com/mholt/caddy/middleware/extensions"
...
@@ -47,7 +48,7 @@ func extParse(c *Controller) ([]string, error) {
...
@@ -47,7 +48,7 @@ func extParse(c *Controller) ([]string, error) {
// resourceExists returns true if the file specified at
// resourceExists returns true if the file specified at
// root + path exists; false otherwise.
// root + path exists; false otherwise.
func
resourceExists
(
root
,
path
string
)
bool
{
func
resourceExists
(
root
,
path
string
)
bool
{
_
,
err
:=
os
.
Stat
(
root
+
path
)
_
,
err
:=
os
.
Stat
(
filepath
.
Join
(
root
,
path
)
)
// technically we should use os.IsNotExist(err)
// technically we should use os.IsNotExist(err)
// but we don't handle any other kinds of errors anyway
// but we don't handle any other kinds of errors anyway
return
err
==
nil
return
err
==
nil
...
...
caddy/setup/headers_test.go
View file @
c431a07a
...
@@ -38,14 +38,14 @@ func TestHeadersParse(t *testing.T) {
...
@@ -38,14 +38,14 @@ func TestHeadersParse(t *testing.T) {
{
`header /foo Foo "Bar Baz"`
,
{
`header /foo Foo "Bar Baz"`
,
false
,
[]
headers
.
Rule
{
false
,
[]
headers
.
Rule
{
{
Path
:
"/foo"
,
Headers
:
[]
headers
.
Header
{
{
Path
:
"/foo"
,
Headers
:
[]
headers
.
Header
{
{
"Foo"
,
"Bar Baz"
},
{
Name
:
"Foo"
,
Value
:
"Bar Baz"
},
}},
}},
}},
}},
{
`header /bar { Foo "Bar Baz" Baz Qux }`
,
{
`header /bar { Foo "Bar Baz" Baz Qux }`
,
false
,
[]
headers
.
Rule
{
false
,
[]
headers
.
Rule
{
{
Path
:
"/bar"
,
Headers
:
[]
headers
.
Header
{
{
Path
:
"/bar"
,
Headers
:
[]
headers
.
Header
{
{
"Foo"
,
"Bar Baz"
},
{
Name
:
"Foo"
,
Value
:
"Bar Baz"
},
{
"Baz"
,
"Qux"
},
{
Name
:
"Baz"
,
Value
:
"Qux"
},
}},
}},
}},
}},
}
}
...
...
dist/CHANGES.txt
View file @
c431a07a
CHANGES
CHANGES
<master>
<master>
- Built with Go 1.6.1
- ...
- New pprof directive for exposing process performance profile
- New expvar directive for exposing memory/GC performance
0.8.3 (April 26, 2016)
- Built with Go 1.6.2
- New pprof middleware for exposing process profiling endpoints
- New expvar middleware for exposing memory/GC performance
- New -restart option to force in-process restarts on Unix systems
- New -restart option to force in-process restarts on Unix systems
- Only fail to start if managed certificate is expired (issue #642)
- Only fail to start if managed certificate is expired (issue #642)
- Toggle case-sensitive path matching with environment variable
- Toggle case-sensitive path matching with environment variable
- File server now adds ETag header for static files
- File server now adds ETag header for static files
- browse: Replace .LinkedPath action with .BreadcrumbMap
- fastcgi: New except clause to exclude paths
- fastcgi: New except clause to exclude paths
- proxy: New max_conns setting to limit max connections per upstream
- proxy: New max_conns setting to limit max connections per upstream
- proxy: Enables replaceable value for name of upstream host
- proxy: New replaceable value for name of upstream host
- templates: New utility actions for dealing with strings
- tls: Customize certificate key with key_type (+ECC)
- tls: Customize certificate key with key_type (+ECC)
- Internal improvements and bug fixes
- tls: Session ticket keys are now rotated
- Many other minor internal improvements and bug fixes
0.8.2 (February 25, 2016)
0.8.2 (February 25, 2016)
- On-demand TLS can obtain certificates during handshakes
- On-demand TLS can obtain certificates during handshakes
...
...
dist/README.txt
View file @
c431a07a
CADDY 0.8.
2
CADDY 0.8.
3
Website
Website
https://caddyserver.com
https://caddyserver.com
...
...
middleware/browse/browse.go
View file @
c431a07a
This diff is collapsed.
Click to expand it.
middleware/browse/browse_test.go
View file @
c431a07a
...
@@ -112,13 +112,12 @@ func TestBrowseHTTPMethods(t *testing.T) {
...
@@ -112,13 +112,12 @@ func TestBrowseHTTPMethods(t *testing.T) {
b
:=
Browse
{
b
:=
Browse
{
Next
:
middleware
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
Next
:
middleware
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
t
.
Fatalf
(
"Next shouldn't be called"
)
return
http
.
StatusTeapot
,
nil
// not t.Fatalf, or we will not see what other methods yield
return
0
,
nil
}),
}),
Root
:
"./testdata"
,
Configs
:
[]
Config
{
Configs
:
[]
Config
{
{
{
PathScope
:
"/photos"
,
PathScope
:
"/photos"
,
Root
:
http
.
Dir
(
"./testdata"
),
Template
:
tmpl
,
Template
:
tmpl
,
},
},
},
},
...
@@ -128,14 +127,8 @@ func TestBrowseHTTPMethods(t *testing.T) {
...
@@ -128,14 +127,8 @@ func TestBrowseHTTPMethods(t *testing.T) {
for
method
,
expected
:=
range
map
[
string
]
int
{
for
method
,
expected
:=
range
map
[
string
]
int
{
http
.
MethodGet
:
http
.
StatusOK
,
http
.
MethodGet
:
http
.
StatusOK
,
http
.
MethodHead
:
http
.
StatusOK
,
http
.
MethodHead
:
http
.
StatusOK
,
http
.
MethodOptions
:
http
.
StatusMethodNotAllowed
,
http
.
MethodOptions
:
http
.
StatusNotImplemented
,
http
.
MethodPost
:
http
.
StatusMethodNotAllowed
,
"PROPFIND"
:
http
.
StatusNotImplemented
,
http
.
MethodPut
:
http
.
StatusMethodNotAllowed
,
http
.
MethodPatch
:
http
.
StatusMethodNotAllowed
,
http
.
MethodDelete
:
http
.
StatusMethodNotAllowed
,
"COPY"
:
http
.
StatusMethodNotAllowed
,
"MOVE"
:
http
.
StatusMethodNotAllowed
,
"MKCOL"
:
http
.
StatusMethodNotAllowed
,
}
{
}
{
req
,
err
:=
http
.
NewRequest
(
method
,
"/photos/"
,
nil
)
req
,
err
:=
http
.
NewRequest
(
method
,
"/photos/"
,
nil
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -160,10 +153,10 @@ func TestBrowseTemplate(t *testing.T) {
...
@@ -160,10 +153,10 @@ func TestBrowseTemplate(t *testing.T) {
t
.
Fatalf
(
"Next shouldn't be called"
)
t
.
Fatalf
(
"Next shouldn't be called"
)
return
0
,
nil
return
0
,
nil
}),
}),
Root
:
"./testdata"
,
Configs
:
[]
Config
{
Configs
:
[]
Config
{
{
{
PathScope
:
"/photos"
,
PathScope
:
"/photos"
,
Root
:
http
.
Dir
(
"./testdata"
),
Template
:
tmpl
,
Template
:
tmpl
,
},
},
},
},
...
@@ -215,16 +208,16 @@ func TestBrowseJson(t *testing.T) {
...
@@ -215,16 +208,16 @@ func TestBrowseJson(t *testing.T) {
t
.
Fatalf
(
"Next shouldn't be called"
)
t
.
Fatalf
(
"Next shouldn't be called"
)
return
0
,
nil
return
0
,
nil
}),
}),
Root
:
"./testdata"
,
Configs
:
[]
Config
{
Configs
:
[]
Config
{
{
{
PathScope
:
"/photos/"
,
PathScope
:
"/photos/"
,
Root
:
http
.
Dir
(
"./testdata"
),
},
},
},
},
}
}
//Getting the listing from the ./testdata/photos, the listing returned will be used to validate test results
//Getting the listing from the ./testdata/photos, the listing returned will be used to validate test results
testDataPath
:=
b
.
Root
+
"/photos/"
testDataPath
:=
filepath
.
Join
(
"./testdata"
,
"photos"
)
file
,
err
:=
os
.
Open
(
testDataPath
)
file
,
err
:=
os
.
Open
(
testDataPath
)
if
err
!=
nil
{
if
err
!=
nil
{
if
os
.
IsPermission
(
err
)
{
if
os
.
IsPermission
(
err
)
{
...
@@ -245,7 +238,7 @@ func TestBrowseJson(t *testing.T) {
...
@@ -245,7 +238,7 @@ func TestBrowseJson(t *testing.T) {
// Tests fail in CI environment because all file mod times are the same for
// Tests fail in CI environment because all file mod times are the same for
// some reason, making the sorting unpredictable. To hack around this,
// some reason, making the sorting unpredictable. To hack around this,
// we ensure here that each file has a different mod time.
// we ensure here that each file has a different mod time.
chTime
:=
f
.
ModTime
()
.
Add
(
-
(
time
.
Duration
(
i
)
*
time
.
Second
))
chTime
:=
f
.
ModTime
()
.
UTC
()
.
Add
(
-
(
time
.
Duration
(
i
)
*
time
.
Second
))
if
err
:=
os
.
Chtimes
(
filepath
.
Join
(
testDataPath
,
name
),
chTime
,
chTime
);
err
!=
nil
{
if
err
:=
os
.
Chtimes
(
filepath
.
Join
(
testDataPath
,
name
),
chTime
,
chTime
);
err
!=
nil
{
t
.
Fatal
(
err
)
t
.
Fatal
(
err
)
}
}
...
@@ -322,7 +315,7 @@ func TestBrowseJson(t *testing.T) {
...
@@ -322,7 +315,7 @@ func TestBrowseJson(t *testing.T) {
code
,
err
:=
b
.
ServeHTTP
(
rec
,
req
)
code
,
err
:=
b
.
ServeHTTP
(
rec
,
req
)
if
code
!=
http
.
StatusOK
{
if
code
!=
http
.
StatusOK
{
t
.
Fatalf
(
"
Wrong status, expected %d, got %d"
,
http
.
StatusOK
,
code
)
t
.
Fatalf
(
"
In test %d: Wrong status, expected %d, got %d"
,
i
,
http
.
StatusOK
,
code
)
}
}
if
rec
.
HeaderMap
.
Get
(
"Content-Type"
)
!=
"application/json; charset=utf-8"
{
if
rec
.
HeaderMap
.
Get
(
"Content-Type"
)
!=
"application/json; charset=utf-8"
{
t
.
Fatalf
(
"Expected Content type to be application/json; charset=utf-8, but got %s "
,
rec
.
HeaderMap
.
Get
(
"Content-Type"
))
t
.
Fatalf
(
"Expected Content type to be application/json; charset=utf-8, but got %s "
,
rec
.
HeaderMap
.
Get
(
"Content-Type"
))
...
...
middleware/context.go
View file @
c431a07a
...
@@ -235,7 +235,7 @@ func (c Context) ToUpper(s string) string {
...
@@ -235,7 +235,7 @@ func (c Context) ToUpper(s string) string {
return
strings
.
ToUpper
(
s
)
return
strings
.
ToUpper
(
s
)
}
}
// Split is a passthrough to strings.Split. It will split the first argument at each instance of the sep
e
rator and return a slice of strings.
// Split is a passthrough to strings.Split. It will split the first argument at each instance of the sep
a
rator and return a slice of strings.
func
(
c
Context
)
Split
(
s
string
,
sep
string
)
[]
string
{
func
(
c
Context
)
Split
(
s
string
,
sep
string
)
[]
string
{
return
strings
.
Split
(
s
,
sep
)
return
strings
.
Split
(
s
,
sep
)
}
}
...
...
middleware/fastcgi/fcgiclient_test.go
View file @
c431a07a
...
@@ -200,7 +200,7 @@ func DisabledTest(t *testing.T) {
...
@@ -200,7 +200,7 @@ func DisabledTest(t *testing.T) {
listener
,
err
:=
net
.
Listen
(
"tcp"
,
ipPort
)
listener
,
err
:=
net
.
Listen
(
"tcp"
,
ipPort
)
if
err
!=
nil
{
if
err
!=
nil
{
// handle error
// handle error
log
.
Println
(
"listener creat
at
ion failed: "
,
err
)
log
.
Println
(
"listener creation failed: "
,
err
)
}
}
srv
:=
new
(
FastCGIServer
)
srv
:=
new
(
FastCGIServer
)
...
...
middleware/fileserver.go
View file @
c431a07a
...
@@ -2,10 +2,12 @@ package middleware
...
@@ -2,10 +2,12 @@ package middleware
import
(
import
(
"fmt"
"fmt"
"math/rand"
"net/http"
"net/http"
"os"
"os"
"path"
"path"
"path/filepath"
"path/filepath"
"strconv"
"strings"
"strings"
)
)
...
@@ -40,12 +42,11 @@ type fileHandler struct {
...
@@ -40,12 +42,11 @@ type fileHandler struct {
}
}
func
(
fh
*
fileHandler
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
func
(
fh
*
fileHandler
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
upath
:=
r
.
URL
.
Path
// r.URL.Path has already been cleaned in caddy/server by path.Clean().
if
!
strings
.
HasPrefix
(
upath
,
"/"
)
{
if
r
.
URL
.
Path
==
""
{
upath
=
"/"
+
upath
r
.
URL
.
Path
=
"/"
r
.
URL
.
Path
=
upath
}
}
return
fh
.
serveFile
(
w
,
r
,
path
.
Clean
(
upath
)
)
return
fh
.
serveFile
(
w
,
r
,
r
.
URL
.
Path
)
}
}
// serveFile writes the specified file to the HTTP response.
// serveFile writes the specified file to the HTTP response.
...
@@ -66,7 +67,8 @@ func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name st
...
@@ -66,7 +67,8 @@ func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name st
return
http
.
StatusForbidden
,
err
return
http
.
StatusForbidden
,
err
}
}
// Likely the server is under load and ran out of file descriptors
// Likely the server is under load and ran out of file descriptors
w
.
Header
()
.
Set
(
"Retry-After"
,
"5"
)
// TODO: 5 seconds enough delay? Or too much?
backoff
:=
int
(
3
+
rand
.
Int31
()
%
3
)
// 3–5 seconds to prevent a stampede
w
.
Header
()
.
Set
(
"Retry-After"
,
strconv
.
Itoa
(
backoff
))
return
http
.
StatusServiceUnavailable
,
err
return
http
.
StatusServiceUnavailable
,
err
}
}
defer
f
.
Close
()
defer
f
.
Close
()
...
@@ -86,13 +88,13 @@ func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name st
...
@@ -86,13 +88,13 @@ func (fh *fileHandler) serveFile(w http.ResponseWriter, r *http.Request, name st
url
:=
r
.
URL
.
Path
url
:=
r
.
URL
.
Path
if
d
.
IsDir
()
{
if
d
.
IsDir
()
{
// Ensure / at end of directory url
// Ensure / at end of directory url
if
url
[
len
(
url
)
-
1
]
!=
'/'
{
if
!
strings
.
HasSuffix
(
url
,
"/"
)
{
redirect
(
w
,
r
,
path
.
Base
(
url
)
+
"/"
)
redirect
(
w
,
r
,
path
.
Base
(
url
)
+
"/"
)
return
http
.
StatusMovedPermanently
,
nil
return
http
.
StatusMovedPermanently
,
nil
}
}
}
else
{
}
else
{
// Ensure no / at end of file url
// Ensure no / at end of file url
if
url
[
len
(
url
)
-
1
]
==
'/'
{
if
strings
.
HasSuffix
(
url
,
"/"
)
{
redirect
(
w
,
r
,
"../"
+
path
.
Base
(
url
))
redirect
(
w
,
r
,
"../"
+
path
.
Base
(
url
))
return
http
.
StatusMovedPermanently
,
nil
return
http
.
StatusMovedPermanently
,
nil
}
}
...
...
middleware/fileserver_test.go
View file @
c431a07a
...
@@ -4,6 +4,7 @@ import (
...
@@ -4,6 +4,7 @@ import (
"errors"
"errors"
"net/http"
"net/http"
"net/http/httptest"
"net/http/httptest"
"net/url"
"os"
"os"
"path/filepath"
"path/filepath"
"strings"
"strings"
...
@@ -11,23 +12,30 @@ import (
...
@@ -11,23 +12,30 @@ import (
"time"
"time"
)
)
var
testDir
=
filepath
.
Join
(
os
.
TempDir
(),
"caddy_testdir"
)
var
(
var
ErrCustom
=
errors
.
New
(
"Custom Error"
)
ErrCustom
=
errors
.
New
(
"Custom Error"
)
testDir
=
filepath
.
Join
(
os
.
TempDir
(),
"caddy_testdir"
)
testWebRoot
=
filepath
.
Join
(
testDir
,
"webroot"
)
)
// testFiles is a map with relative paths to test files as keys and file content as values.
// testFiles is a map with relative paths to test files as keys and file content as values.
// The map represents the following structure:
// The map represents the following structure:
// - $TEMP/caddy_testdir/
// - $TEMP/caddy_testdir/
// '-- file1.html
// '-- unreachable.html
// '-- dirwithindex/
// '-- webroot/
// '---- index.html
// '---- file1.html
// '-- dir/
// '---- dirwithindex/
// '---- file2.html
// '------ index.html
// '---- hidden.html
// '---- dir/
// '------ file2.html
// '------ hidden.html
var
testFiles
=
map
[
string
]
string
{
var
testFiles
=
map
[
string
]
string
{
"file1.html"
:
"<h1>file1.html</h1>"
,
"unreachable.html"
:
"<h1>must not leak</h1>"
,
filepath
.
Join
(
"dirwithindex"
,
"index.html"
)
:
"<h1>dirwithindex/index.html</h1>"
,
filepath
.
Join
(
"webroot"
,
"file1.html"
)
:
"<h1>file1.html</h1>"
,
filepath
.
Join
(
"dir"
,
"file2.html"
)
:
"<h1>dir/file2.html</h1>"
,
filepath
.
Join
(
"webroot"
,
"dirwithindex"
,
"index.html"
)
:
"<h1>dirwithindex/index.html</h1>"
,
filepath
.
Join
(
"dir"
,
"hidden.html"
)
:
"<h1>dir/hidden.html</h1>"
,
filepath
.
Join
(
"webroot"
,
"dir"
,
"file2.html"
)
:
"<h1>dir/file2.html</h1>"
,
filepath
.
Join
(
"webroot"
,
"dir"
,
"hidden.html"
)
:
"<h1>dir/hidden.html</h1>"
,
}
}
// TestServeHTTP covers positive scenarios when serving files.
// TestServeHTTP covers positive scenarios when serving files.
...
@@ -36,7 +44,7 @@ func TestServeHTTP(t *testing.T) {
...
@@ -36,7 +44,7 @@ func TestServeHTTP(t *testing.T) {
beforeServeHTTPTest
(
t
)
beforeServeHTTPTest
(
t
)
defer
afterServeHTTPTest
(
t
)
defer
afterServeHTTPTest
(
t
)
fileserver
:=
FileServer
(
http
.
Dir
(
test
Dir
),
[]
string
{
"dir/hidden.html"
})
fileserver
:=
FileServer
(
http
.
Dir
(
test
WebRoot
),
[]
string
{
"dir/hidden.html"
})
movedPermanently
:=
"Moved Permanently"
movedPermanently
:=
"Moved Permanently"
...
@@ -142,11 +150,20 @@ func TestServeHTTP(t *testing.T) {
...
@@ -142,11 +150,20 @@ func TestServeHTTP(t *testing.T) {
url
:
"https://foo/hidden.html"
,
url
:
"https://foo/hidden.html"
,
expectedStatus
:
http
.
StatusNotFound
,
expectedStatus
:
http
.
StatusNotFound
,
},
},
// Test 17 - try to get below the root directory.
{
url
:
"https://foo/%2f..%2funreachable.html"
,
expectedStatus
:
http
.
StatusNotFound
,
},
}
}
for
i
,
test
:=
range
tests
{
for
i
,
test
:=
range
tests
{
responseRecorder
:=
httptest
.
NewRecorder
()
responseRecorder
:=
httptest
.
NewRecorder
()
request
,
err
:=
http
.
NewRequest
(
"GET"
,
test
.
url
,
strings
.
NewReader
(
""
))
request
,
err
:=
http
.
NewRequest
(
"GET"
,
test
.
url
,
nil
)
// prevent any URL sanitization within Go: we need unmodified paths here
if
u
,
_
:=
url
.
Parse
(
test
.
url
);
u
.
RawPath
!=
""
{
request
.
URL
.
Path
=
u
.
RawPath
}
status
,
err
:=
fileserver
.
ServeHTTP
(
responseRecorder
,
request
)
status
,
err
:=
fileserver
.
ServeHTTP
(
responseRecorder
,
request
)
etag
:=
responseRecorder
.
Header
()
.
Get
(
"Etag"
)
etag
:=
responseRecorder
.
Header
()
.
Get
(
"Etag"
)
...
@@ -176,7 +193,7 @@ func TestServeHTTP(t *testing.T) {
...
@@ -176,7 +193,7 @@ func TestServeHTTP(t *testing.T) {
// beforeServeHTTPTest creates a test directory with the structure, defined in the variable testFiles
// beforeServeHTTPTest creates a test directory with the structure, defined in the variable testFiles
func
beforeServeHTTPTest
(
t
*
testing
.
T
)
{
func
beforeServeHTTPTest
(
t
*
testing
.
T
)
{
// make the root test dir
// make the root test dir
err
:=
os
.
Mkdir
(
testDir
,
os
.
ModePerm
)
err
:=
os
.
Mkdir
All
(
testWebRoot
,
os
.
ModePerm
)
if
err
!=
nil
{
if
err
!=
nil
{
if
!
os
.
IsExist
(
err
)
{
if
!
os
.
IsExist
(
err
)
{
t
.
Fatalf
(
"Failed to create test dir. Error was: %v"
,
err
)
t
.
Fatalf
(
"Failed to create test dir. Error was: %v"
,
err
)
...
...
middleware/headers/headers.go
View file @
c431a07a
...
@@ -20,13 +20,14 @@ type Headers struct {
...
@@ -20,13 +20,14 @@ type Headers struct {
// ServeHTTP implements the middleware.Handler interface and serves requests,
// ServeHTTP implements the middleware.Handler interface and serves requests,
// setting headers on the response according to the configured rules.
// setting headers on the response according to the configured rules.
func
(
h
Headers
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
func
(
h
Headers
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
replacer
:=
middleware
.
NewReplacer
(
r
,
nil
,
""
)
for
_
,
rule
:=
range
h
.
Rules
{
for
_
,
rule
:=
range
h
.
Rules
{
if
middleware
.
Path
(
r
.
URL
.
Path
)
.
Matches
(
rule
.
Path
)
{
if
middleware
.
Path
(
r
.
URL
.
Path
)
.
Matches
(
rule
.
Path
)
{
for
_
,
header
:=
range
rule
.
Headers
{
for
_
,
header
:=
range
rule
.
Headers
{
if
strings
.
HasPrefix
(
header
.
Name
,
"-"
)
{
if
strings
.
HasPrefix
(
header
.
Name
,
"-"
)
{
w
.
Header
()
.
Del
(
strings
.
TrimLeft
(
header
.
Name
,
"-"
))
w
.
Header
()
.
Del
(
strings
.
TrimLeft
(
header
.
Name
,
"-"
))
}
else
{
}
else
{
w
.
Header
()
.
Set
(
header
.
Name
,
header
.
Value
)
w
.
Header
()
.
Set
(
header
.
Name
,
replacer
.
Replace
(
header
.
Value
)
)
}
}
}
}
}
}
...
...
middleware/headers/headers_test.go
View file @
c431a07a
...
@@ -3,12 +3,17 @@ package headers
...
@@ -3,12 +3,17 @@ package headers
import
(
import
(
"net/http"
"net/http"
"net/http/httptest"
"net/http/httptest"
"os"
"testing"
"testing"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware"
)
)
func
TestHeaders
(
t
*
testing
.
T
)
{
func
TestHeaders
(
t
*
testing
.
T
)
{
hostname
,
err
:=
os
.
Hostname
()
if
err
!=
nil
{
t
.
Fatalf
(
"Could not determine hostname: %v"
,
err
)
}
for
i
,
test
:=
range
[]
struct
{
for
i
,
test
:=
range
[]
struct
{
from
string
from
string
name
string
name
string
...
@@ -17,6 +22,7 @@ func TestHeaders(t *testing.T) {
...
@@ -17,6 +22,7 @@ func TestHeaders(t *testing.T) {
{
"/a"
,
"Foo"
,
"Bar"
},
{
"/a"
,
"Foo"
,
"Bar"
},
{
"/a"
,
"Bar"
,
""
},
{
"/a"
,
"Bar"
,
""
},
{
"/a"
,
"Baz"
,
""
},
{
"/a"
,
"Baz"
,
""
},
{
"/a"
,
"ServerName"
,
hostname
},
{
"/b"
,
"Foo"
,
""
},
{
"/b"
,
"Foo"
,
""
},
{
"/b"
,
"Bar"
,
"Removed in /a"
},
{
"/b"
,
"Bar"
,
"Removed in /a"
},
}
{
}
{
...
@@ -27,6 +33,7 @@ func TestHeaders(t *testing.T) {
...
@@ -27,6 +33,7 @@ func TestHeaders(t *testing.T) {
Rules
:
[]
Rule
{
Rules
:
[]
Rule
{
{
Path
:
"/a"
,
Headers
:
[]
Header
{
{
Path
:
"/a"
,
Headers
:
[]
Header
{
{
Name
:
"Foo"
,
Value
:
"Bar"
},
{
Name
:
"Foo"
,
Value
:
"Bar"
},
{
Name
:
"ServerName"
,
Value
:
"{hostname}"
},
{
Name
:
"-Bar"
},
{
Name
:
"-Bar"
},
}},
}},
},
},
...
...
middleware/proxy/proxy.go
View file @
c431a07a
...
@@ -3,8 +3,10 @@ package proxy
...
@@ -3,8 +3,10 @@ package proxy
import
(
import
(
"errors"
"errors"
"net"
"net/http"
"net/http"
"net/url"
"net/url"
"strings"
"sync/atomic"
"sync/atomic"
"time"
"time"
...
@@ -41,7 +43,8 @@ type UpstreamHost struct {
...
@@ -41,7 +43,8 @@ type UpstreamHost struct {
Fails
int32
Fails
int32
FailTimeout
time
.
Duration
FailTimeout
time
.
Duration
Unhealthy
bool
Unhealthy
bool
ExtraHeaders
http
.
Header
UpstreamHeaders
http
.
Header
DownstreamHeaders
http
.
Header
CheckDown
UpstreamHostDownFunc
CheckDown
UpstreamHostDownFunc
WithoutPathPrefix
string
WithoutPathPrefix
string
MaxConns
int64
MaxConns
int64
...
@@ -75,71 +78,160 @@ var tryDuration = 60 * time.Second
...
@@ -75,71 +78,160 @@ var tryDuration = 60 * time.Second
// ServeHTTP satisfies the middleware.Handler interface.
// ServeHTTP satisfies the middleware.Handler interface.
func
(
p
Proxy
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
func
(
p
Proxy
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
for
_
,
upstream
:=
range
p
.
Upstreams
{
for
_
,
upstream
:=
range
p
.
Upstreams
{
if
middleware
.
Path
(
r
.
URL
.
Path
)
.
Matches
(
upstream
.
From
())
&&
upstream
.
AllowedPath
(
r
.
URL
.
Path
)
{
if
!
middleware
.
Path
(
r
.
URL
.
Path
)
.
Matches
(
upstream
.
From
())
||
var
replacer
middleware
.
Replacer
!
upstream
.
AllowedPath
(
r
.
URL
.
Path
)
{
start
:=
time
.
Now
()
continue
requestHost
:=
r
.
Host
}
// Since Select() should give us "up" hosts, keep retrying
var
replacer
middleware
.
Replacer
// hosts until timeout (or until we get a nil host).
start
:=
time
.
Now
()
for
time
.
Now
()
.
Sub
(
start
)
<
tryDuration
{
host
:=
upstream
.
Select
()
outreq
:=
createUpstreamRequest
(
r
)
if
host
==
nil
{
return
http
.
StatusBadGateway
,
errUnreachable
}
proxy
:=
host
.
ReverseProxy
r
.
Host
=
host
.
Name
if
rr
,
ok
:=
w
.
(
*
middleware
.
ResponseRecorder
);
ok
&&
rr
.
Replacer
!=
nil
{
rr
.
Replacer
.
Set
(
"upstream"
,
host
.
Name
)
}
if
baseURL
,
err
:=
url
.
Parse
(
host
.
Name
);
err
==
nil
{
// Since Select() should give us "up" hosts, keep retrying
r
.
Host
=
baseURL
.
Host
// hosts until timeout (or until we get a nil host).
if
proxy
==
nil
{
for
time
.
Now
()
.
Sub
(
start
)
<
tryDuration
{
proxy
=
NewSingleHostReverseProxy
(
baseURL
,
host
.
WithoutPathPrefix
)
host
:=
upstream
.
Select
()
}
if
host
==
nil
{
}
else
if
proxy
==
nil
{
return
http
.
StatusBadGateway
,
errUnreachable
return
http
.
StatusInternalServerError
,
err
}
if
rr
,
ok
:=
w
.
(
*
middleware
.
ResponseRecorder
);
ok
&&
rr
.
Replacer
!=
nil
{
rr
.
Replacer
.
Set
(
"upstream"
,
host
.
Name
)
}
outreq
.
Host
=
host
.
Name
if
host
.
UpstreamHeaders
!=
nil
{
if
replacer
==
nil
{
rHost
:=
r
.
Host
replacer
=
middleware
.
NewReplacer
(
r
,
nil
,
""
)
outreq
.
Host
=
rHost
}
}
var
extraHeaders
http
.
Header
if
v
,
ok
:=
host
.
UpstreamHeaders
[
"Host"
];
ok
{
if
host
.
ExtraHeaders
!=
nil
{
r
.
Host
=
replacer
.
Replace
(
v
[
len
(
v
)
-
1
])
extraHeaders
=
make
(
http
.
Header
)
if
replacer
==
nil
{
rHost
:=
r
.
Host
r
.
Host
=
requestHost
replacer
=
middleware
.
NewReplacer
(
r
,
nil
,
""
)
r
.
Host
=
rHost
}
for
header
,
values
:=
range
host
.
ExtraHeaders
{
for
_
,
value
:=
range
values
{
extraHeaders
.
Add
(
header
,
replacer
.
Replace
(
value
))
if
header
==
"Host"
{
r
.
Host
=
replacer
.
Replace
(
value
)
}
}
}
}
}
// Modify headers for request that will be sent to the upstream host
upHeaders
:=
createHeadersByRules
(
host
.
UpstreamHeaders
,
r
.
Header
,
replacer
)
for
k
,
v
:=
range
upHeaders
{
outreq
.
Header
[
k
]
=
v
}
}
atomic
.
AddInt64
(
&
host
.
Conns
,
1
)
var
downHeaderUpdateFn
respUpdateFn
backendErr
:=
proxy
.
ServeHTTP
(
w
,
r
,
extraHeaders
)
if
host
.
DownstreamHeaders
!=
nil
{
atomic
.
AddInt64
(
&
host
.
Conns
,
-
1
)
if
replacer
==
nil
{
if
backendErr
==
nil
{
rHost
:=
r
.
Host
return
0
,
nil
replacer
=
middleware
.
NewReplacer
(
r
,
nil
,
""
)
outreq
.
Host
=
rHost
}
}
timeout
:=
host
.
FailTimeout
//Creates a function that is used to update headers the response received by the reverse proxy
if
timeout
==
0
{
downHeaderUpdateFn
=
createRespHeaderUpdateFn
(
host
.
DownstreamHeaders
,
replacer
)
timeout
=
10
*
time
.
Second
}
proxy
:=
host
.
ReverseProxy
if
baseURL
,
err
:=
url
.
Parse
(
host
.
Name
);
err
==
nil
{
r
.
Host
=
baseURL
.
Host
if
proxy
==
nil
{
proxy
=
NewSingleHostReverseProxy
(
baseURL
,
host
.
WithoutPathPrefix
)
}
}
atomic
.
AddInt32
(
&
host
.
Fails
,
1
)
}
else
if
proxy
==
nil
{
go
func
(
host
*
UpstreamHost
,
timeout
time
.
Duration
)
{
return
http
.
StatusInternalServerError
,
err
time
.
Sleep
(
timeout
)
}
atomic
.
AddInt32
(
&
host
.
Fails
,
-
1
)
}(
host
,
timeout
)
atomic
.
AddInt64
(
&
host
.
Conns
,
1
)
backendErr
:=
proxy
.
ServeHTTP
(
w
,
outreq
,
downHeaderUpdateFn
)
atomic
.
AddInt64
(
&
host
.
Conns
,
-
1
)
if
backendErr
==
nil
{
return
0
,
nil
}
timeout
:=
host
.
FailTimeout
if
timeout
==
0
{
timeout
=
10
*
time
.
Second
}
}
return
http
.
StatusBadGateway
,
errUnreachable
atomic
.
AddInt32
(
&
host
.
Fails
,
1
)
go
func
(
host
*
UpstreamHost
,
timeout
time
.
Duration
)
{
time
.
Sleep
(
timeout
)
atomic
.
AddInt32
(
&
host
.
Fails
,
-
1
)
}(
host
,
timeout
)
}
}
return
http
.
StatusBadGateway
,
errUnreachable
}
}
return
p
.
Next
.
ServeHTTP
(
w
,
r
)
return
p
.
Next
.
ServeHTTP
(
w
,
r
)
}
}
// createUpstremRequest shallow-copies r into a new request
// that can be sent upstream.
func
createUpstreamRequest
(
r
*
http
.
Request
)
*
http
.
Request
{
outreq
:=
new
(
http
.
Request
)
*
outreq
=
*
r
// includes shallow copies of maps, but okay
// Remove hop-by-hop headers to the backend. Especially
// important is "Connection" because we want a persistent
// connection, regardless of what the client sent to us. This
// is modifying the same underlying map from r (shallow
// copied above) so we only copy it if necessary.
for
_
,
h
:=
range
hopHeaders
{
if
outreq
.
Header
.
Get
(
h
)
!=
""
{
outreq
.
Header
=
make
(
http
.
Header
)
copyHeader
(
outreq
.
Header
,
r
.
Header
)
outreq
.
Header
.
Del
(
h
)
}
}
if
clientIP
,
_
,
err
:=
net
.
SplitHostPort
(
r
.
RemoteAddr
);
err
==
nil
{
// If we aren't the first proxy, retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if
prior
,
ok
:=
outreq
.
Header
[
"X-Forwarded-For"
];
ok
{
clientIP
=
strings
.
Join
(
prior
,
", "
)
+
", "
+
clientIP
}
outreq
.
Header
.
Set
(
"X-Forwarded-For"
,
clientIP
)
}
return
outreq
}
func
createRespHeaderUpdateFn
(
rules
http
.
Header
,
replacer
middleware
.
Replacer
)
respUpdateFn
{
return
func
(
resp
*
http
.
Response
)
{
newHeaders
:=
createHeadersByRules
(
rules
,
resp
.
Header
,
replacer
)
for
h
,
v
:=
range
newHeaders
{
resp
.
Header
[
h
]
=
v
}
}
}
func
createHeadersByRules
(
rules
http
.
Header
,
base
http
.
Header
,
repl
middleware
.
Replacer
)
http
.
Header
{
newHeaders
:=
make
(
http
.
Header
)
for
header
,
values
:=
range
rules
{
if
strings
.
HasPrefix
(
header
,
"+"
)
{
header
=
strings
.
TrimLeft
(
header
,
"+"
)
add
(
newHeaders
,
header
,
base
[
header
])
applyEach
(
values
,
repl
.
Replace
)
add
(
newHeaders
,
header
,
values
)
}
else
if
strings
.
HasPrefix
(
header
,
"-"
)
{
base
.
Del
(
strings
.
TrimLeft
(
header
,
"-"
))
}
else
if
_
,
ok
:=
base
[
header
];
ok
{
applyEach
(
values
,
repl
.
Replace
)
for
_
,
v
:=
range
values
{
newHeaders
.
Set
(
header
,
v
)
}
}
else
{
applyEach
(
values
,
repl
.
Replace
)
add
(
newHeaders
,
header
,
values
)
add
(
newHeaders
,
header
,
base
[
header
])
}
}
return
newHeaders
}
func
applyEach
(
values
[]
string
,
mapFn
func
(
string
)
string
)
{
for
i
,
v
:=
range
values
{
values
[
i
]
=
mapFn
(
v
)
}
}
func
add
(
base
http
.
Header
,
header
string
,
values
[]
string
)
{
for
_
,
v
:=
range
values
{
base
.
Add
(
header
,
v
)
}
}
middleware/proxy/proxy_test.go
View file @
c431a07a
...
@@ -348,6 +348,141 @@ func TestUnixSocketProxyPaths(t *testing.T) {
...
@@ -348,6 +348,141 @@ func TestUnixSocketProxyPaths(t *testing.T) {
}
}
}
}
func
TestUpstreamHeadersUpdate
(
t
*
testing
.
T
)
{
log
.
SetOutput
(
ioutil
.
Discard
)
defer
log
.
SetOutput
(
os
.
Stderr
)
var
actualHeaders
http
.
Header
backend
:=
httptest
.
NewServer
(
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
w
.
Write
([]
byte
(
"Hello, client"
))
actualHeaders
=
r
.
Header
}))
defer
backend
.
Close
()
upstream
:=
newFakeUpstream
(
backend
.
URL
,
false
)
upstream
.
host
.
UpstreamHeaders
=
http
.
Header
{
"Connection"
:
{
"{>Connection}"
},
"Upgrade"
:
{
"{>Upgrade}"
},
"+Merge-Me"
:
{
"Merge-Value"
},
"+Add-Me"
:
{
"Add-Value"
},
"-Remove-Me"
:
{
""
},
"Replace-Me"
:
{
"{hostname}"
},
}
// set up proxy
p
:=
&
Proxy
{
Upstreams
:
[]
Upstream
{
upstream
},
}
// create request and response recorder
r
,
err
:=
http
.
NewRequest
(
"GET"
,
"/"
,
nil
)
if
err
!=
nil
{
t
.
Fatalf
(
"Failed to create request: %v"
,
err
)
}
w
:=
httptest
.
NewRecorder
()
//add initial headers
r
.
Header
.
Add
(
"Merge-Me"
,
"Initial"
)
r
.
Header
.
Add
(
"Remove-Me"
,
"Remove-Value"
)
r
.
Header
.
Add
(
"Replace-Me"
,
"Replace-Value"
)
p
.
ServeHTTP
(
w
,
r
)
replacer
:=
middleware
.
NewReplacer
(
r
,
nil
,
""
)
headerKey
:=
"Merge-Me"
values
,
ok
:=
actualHeaders
[
headerKey
]
if
!
ok
{
t
.
Errorf
(
"Request sent to upstream backend does not contain expected %v header. Expected header to be added"
,
headerKey
)
}
else
if
len
(
values
)
<
2
&&
(
values
[
0
]
!=
"Initial"
||
values
[
1
]
!=
replacer
.
Replace
(
"{hostname}"
))
{
t
.
Errorf
(
"Values for proxy header `+Merge-Me` should be merged. Got %v"
,
values
)
}
headerKey
=
"Add-Me"
if
_
,
ok
:=
actualHeaders
[
headerKey
];
!
ok
{
t
.
Errorf
(
"Request sent to upstream backend does not contain expected %v header"
,
headerKey
)
}
headerKey
=
"Remove-Me"
if
_
,
ok
:=
actualHeaders
[
headerKey
];
ok
{
t
.
Errorf
(
"Request sent to upstream backend should not contain %v header"
,
headerKey
)
}
headerKey
=
"Replace-Me"
headerValue
:=
replacer
.
Replace
(
"{hostname}"
)
value
,
ok
:=
actualHeaders
[
headerKey
]
if
!
ok
{
t
.
Errorf
(
"Request sent to upstream backend should not remove %v header"
,
headerKey
)
}
else
if
len
(
value
)
>
0
&&
headerValue
!=
value
[
0
]
{
t
.
Errorf
(
"Request sent to upstream backend should replace value of %v header with %v. Instead value was %v"
,
headerKey
,
headerValue
,
value
)
}
}
func
TestDownstreamHeadersUpdate
(
t
*
testing
.
T
)
{
log
.
SetOutput
(
ioutil
.
Discard
)
defer
log
.
SetOutput
(
os
.
Stderr
)
backend
:=
httptest
.
NewServer
(
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
w
.
Header
()
.
Add
(
"Merge-Me"
,
"Initial"
)
w
.
Header
()
.
Add
(
"Remove-Me"
,
"Remove-Value"
)
w
.
Header
()
.
Add
(
"Replace-Me"
,
"Replace-Value"
)
w
.
Write
([]
byte
(
"Hello, client"
))
}))
defer
backend
.
Close
()
upstream
:=
newFakeUpstream
(
backend
.
URL
,
false
)
upstream
.
host
.
DownstreamHeaders
=
http
.
Header
{
"+Merge-Me"
:
{
"Merge-Value"
},
"+Add-Me"
:
{
"Add-Value"
},
"-Remove-Me"
:
{
""
},
"Replace-Me"
:
{
"{hostname}"
},
}
// set up proxy
p
:=
&
Proxy
{
Upstreams
:
[]
Upstream
{
upstream
},
}
// create request and response recorder
r
,
err
:=
http
.
NewRequest
(
"GET"
,
"/"
,
nil
)
if
err
!=
nil
{
t
.
Fatalf
(
"Failed to create request: %v"
,
err
)
}
w
:=
httptest
.
NewRecorder
()
p
.
ServeHTTP
(
w
,
r
)
replacer
:=
middleware
.
NewReplacer
(
r
,
nil
,
""
)
actualHeaders
:=
w
.
Header
()
headerKey
:=
"Merge-Me"
values
,
ok
:=
actualHeaders
[
headerKey
]
if
!
ok
{
t
.
Errorf
(
"Downstream response does not contain expected %v header. Expected header should be added"
,
headerKey
)
}
else
if
len
(
values
)
<
2
&&
(
values
[
0
]
!=
"Initial"
||
values
[
1
]
!=
replacer
.
Replace
(
"{hostname}"
))
{
t
.
Errorf
(
"Values for header `+Merge-Me` should be merged. Got %v"
,
values
)
}
headerKey
=
"Add-Me"
if
_
,
ok
:=
actualHeaders
[
headerKey
];
!
ok
{
t
.
Errorf
(
"Downstream response does not contain expected %v header"
,
headerKey
)
}
headerKey
=
"Remove-Me"
if
_
,
ok
:=
actualHeaders
[
headerKey
];
ok
{
t
.
Errorf
(
"Downstream response should not contain %v header received from upstream"
,
headerKey
)
}
headerKey
=
"Replace-Me"
headerValue
:=
replacer
.
Replace
(
"{hostname}"
)
value
,
ok
:=
actualHeaders
[
headerKey
]
if
!
ok
{
t
.
Errorf
(
"Downstream response should contain %v header and not remove it"
,
headerKey
)
}
else
if
len
(
value
)
>
0
&&
headerValue
!=
value
[
0
]
{
t
.
Errorf
(
"Downstream response should have header %v with value %v. Instead value was %v"
,
headerKey
,
headerValue
,
value
)
}
}
func
newFakeUpstream
(
name
string
,
insecure
bool
)
*
fakeUpstream
{
func
newFakeUpstream
(
name
string
,
insecure
bool
)
*
fakeUpstream
{
uri
,
_
:=
url
.
Parse
(
name
)
uri
,
_
:=
url
.
Parse
(
name
)
u
:=
&
fakeUpstream
{
u
:=
&
fakeUpstream
{
...
@@ -410,7 +545,7 @@ func (u *fakeWsUpstream) Select() *UpstreamHost {
...
@@ -410,7 +545,7 @@ func (u *fakeWsUpstream) Select() *UpstreamHost {
return
&
UpstreamHost
{
return
&
UpstreamHost
{
Name
:
u
.
name
,
Name
:
u
.
name
,
ReverseProxy
:
NewSingleHostReverseProxy
(
uri
,
u
.
without
),
ReverseProxy
:
NewSingleHostReverseProxy
(
uri
,
u
.
without
),
Extra
Headers
:
http
.
Header
{
Upstream
Headers
:
http
.
Header
{
"Connection"
:
{
"{>Connection}"
},
"Connection"
:
{
"{>Connection}"
},
"Upgrade"
:
{
"{>Upgrade}"
}},
"Upgrade"
:
{
"{>Upgrade}"
}},
}
}
...
...
middleware/proxy/reverseproxy.go
View file @
c431a07a
...
@@ -154,57 +154,25 @@ var InsecureTransport http.RoundTripper = &http.Transport{
...
@@ -154,57 +154,25 @@ var InsecureTransport http.RoundTripper = &http.Transport{
TLSClientConfig
:
&
tls
.
Config
{
InsecureSkipVerify
:
true
},
TLSClientConfig
:
&
tls
.
Config
{
InsecureSkipVerify
:
true
},
}
}
func
(
p
*
ReverseProxy
)
ServeHTTP
(
rw
http
.
ResponseWriter
,
req
*
http
.
Request
,
extraHeaders
http
.
Header
)
error
{
type
respUpdateFn
func
(
resp
*
http
.
Response
)
func
(
p
*
ReverseProxy
)
ServeHTTP
(
rw
http
.
ResponseWriter
,
outreq
*
http
.
Request
,
respUpdateFn
respUpdateFn
)
error
{
transport
:=
p
.
Transport
transport
:=
p
.
Transport
if
transport
==
nil
{
if
transport
==
nil
{
transport
=
http
.
DefaultTransport
transport
=
http
.
DefaultTransport
}
}
outreq
:=
new
(
http
.
Request
)
*
outreq
=
*
req
// includes shallow copies of maps, but okay
p
.
Director
(
outreq
)
p
.
Director
(
outreq
)
outreq
.
Proto
=
"HTTP/1.1"
outreq
.
Proto
=
"HTTP/1.1"
outreq
.
ProtoMajor
=
1
outreq
.
ProtoMajor
=
1
outreq
.
ProtoMinor
=
1
outreq
.
ProtoMinor
=
1
outreq
.
Close
=
false
outreq
.
Close
=
false
// Remove hop-by-hop headers to the backend. Especially
// important is "Connection" because we want a persistent
// connection, regardless of what the client sent to us. This
// is modifying the same underlying map from req (shallow
// copied above) so we only copy it if necessary.
copiedHeaders
:=
false
for
_
,
h
:=
range
hopHeaders
{
if
outreq
.
Header
.
Get
(
h
)
!=
""
{
if
!
copiedHeaders
{
outreq
.
Header
=
make
(
http
.
Header
)
copyHeader
(
outreq
.
Header
,
req
.
Header
)
copiedHeaders
=
true
}
outreq
.
Header
.
Del
(
h
)
}
}
if
clientIP
,
_
,
err
:=
net
.
SplitHostPort
(
req
.
RemoteAddr
);
err
==
nil
{
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if
prior
,
ok
:=
outreq
.
Header
[
"X-Forwarded-For"
];
ok
{
clientIP
=
strings
.
Join
(
prior
,
", "
)
+
", "
+
clientIP
}
outreq
.
Header
.
Set
(
"X-Forwarded-For"
,
clientIP
)
}
if
extraHeaders
!=
nil
{
for
k
,
v
:=
range
extraHeaders
{
outreq
.
Header
[
k
]
=
v
}
}
res
,
err
:=
transport
.
RoundTrip
(
outreq
)
res
,
err
:=
transport
.
RoundTrip
(
outreq
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
else
if
respUpdateFn
!=
nil
{
respUpdateFn
(
res
)
}
}
if
res
.
StatusCode
==
http
.
StatusSwitchingProtocols
&&
strings
.
ToLower
(
res
.
Header
.
Get
(
"Upgrade"
))
==
"websocket"
{
if
res
.
StatusCode
==
http
.
StatusSwitchingProtocols
&&
strings
.
ToLower
(
res
.
Header
.
Get
(
"Upgrade"
))
==
"websocket"
{
...
@@ -237,9 +205,7 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, extr
...
@@ -237,9 +205,7 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request, extr
for
_
,
h
:=
range
hopHeaders
{
for
_
,
h
:=
range
hopHeaders
{
res
.
Header
.
Del
(
h
)
res
.
Header
.
Del
(
h
)
}
}
copyHeader
(
rw
.
Header
(),
res
.
Header
)
copyHeader
(
rw
.
Header
(),
res
.
Header
)
rw
.
WriteHeader
(
res
.
StatusCode
)
rw
.
WriteHeader
(
res
.
StatusCode
)
p
.
copyResponse
(
rw
,
res
.
Body
)
p
.
copyResponse
(
rw
,
res
.
Body
)
}
}
...
@@ -260,7 +226,6 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
...
@@ -260,7 +226,6 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
dst
=
mlw
dst
=
mlw
}
}
}
}
io
.
Copy
(
dst
,
src
)
io
.
Copy
(
dst
,
src
)
}
}
...
...
middleware/proxy/upstream.go
View file @
c431a07a
...
@@ -20,7 +20,8 @@ var (
...
@@ -20,7 +20,8 @@ var (
type
staticUpstream
struct
{
type
staticUpstream
struct
{
from
string
from
string
proxyHeaders
http
.
Header
upstreamHeaders
http
.
Header
downstreamHeaders
http
.
Header
Hosts
HostPool
Hosts
HostPool
Policy
Policy
Policy
Policy
insecureSkipVerify
bool
insecureSkipVerify
bool
...
@@ -42,13 +43,14 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
...
@@ -42,13 +43,14 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
var
upstreams
[]
Upstream
var
upstreams
[]
Upstream
for
c
.
Next
()
{
for
c
.
Next
()
{
upstream
:=
&
staticUpstream
{
upstream
:=
&
staticUpstream
{
from
:
""
,
from
:
""
,
proxyHeaders
:
make
(
http
.
Header
),
upstreamHeaders
:
make
(
http
.
Header
),
Hosts
:
nil
,
downstreamHeaders
:
make
(
http
.
Header
),
Policy
:
&
Random
{},
Hosts
:
nil
,
FailTimeout
:
10
*
time
.
Second
,
Policy
:
&
Random
{},
MaxFails
:
1
,
FailTimeout
:
10
*
time
.
Second
,
MaxConns
:
0
,
MaxFails
:
1
,
MaxConns
:
0
,
}
}
if
!
c
.
Args
(
&
upstream
.
from
)
{
if
!
c
.
Args
(
&
upstream
.
from
)
{
...
@@ -97,12 +99,13 @@ func (u *staticUpstream) NewHost(host string) (*UpstreamHost, error) {
...
@@ -97,12 +99,13 @@ func (u *staticUpstream) NewHost(host string) (*UpstreamHost, error) {
host
=
"http://"
+
host
host
=
"http://"
+
host
}
}
uh
:=
&
UpstreamHost
{
uh
:=
&
UpstreamHost
{
Name
:
host
,
Name
:
host
,
Conns
:
0
,
Conns
:
0
,
Fails
:
0
,
Fails
:
0
,
FailTimeout
:
u
.
FailTimeout
,
FailTimeout
:
u
.
FailTimeout
,
Unhealthy
:
false
,
Unhealthy
:
false
,
ExtraHeaders
:
u
.
proxyHeaders
,
UpstreamHeaders
:
u
.
upstreamHeaders
,
DownstreamHeaders
:
u
.
downstreamHeaders
,
CheckDown
:
func
(
u
*
staticUpstream
)
UpstreamHostDownFunc
{
CheckDown
:
func
(
u
*
staticUpstream
)
UpstreamHostDownFunc
{
return
func
(
uh
*
UpstreamHost
)
bool
{
return
func
(
uh
*
UpstreamHost
)
bool
{
if
uh
.
Unhealthy
{
if
uh
.
Unhealthy
{
...
@@ -182,15 +185,23 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
...
@@ -182,15 +185,23 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
}
}
u
.
HealthCheck
.
Interval
=
dur
u
.
HealthCheck
.
Interval
=
dur
}
}
case
"header_upstream"
:
fallthrough
case
"proxy_header"
:
case
"proxy_header"
:
var
header
,
value
string
var
header
,
value
string
if
!
c
.
Args
(
&
header
,
&
value
)
{
if
!
c
.
Args
(
&
header
,
&
value
)
{
return
c
.
ArgErr
()
return
c
.
ArgErr
()
}
}
u
.
proxyHeaders
.
Add
(
header
,
value
)
u
.
upstreamHeaders
.
Add
(
header
,
value
)
case
"header_downstream"
:
var
header
,
value
string
if
!
c
.
Args
(
&
header
,
&
value
)
{
return
c
.
ArgErr
()
}
u
.
downstreamHeaders
.
Add
(
header
,
value
)
case
"websocket"
:
case
"websocket"
:
u
.
proxy
Headers
.
Add
(
"Connection"
,
"{>Connection}"
)
u
.
upstream
Headers
.
Add
(
"Connection"
,
"{>Connection}"
)
u
.
proxy
Headers
.
Add
(
"Upgrade"
,
"{>Upgrade}"
)
u
.
upstream
Headers
.
Add
(
"Upgrade"
,
"{>Upgrade}"
)
case
"without"
:
case
"without"
:
if
!
c
.
NextArg
()
{
if
!
c
.
NextArg
()
{
return
c
.
ArgErr
()
return
c
.
ArgErr
()
...
...
middleware/replacer.go
View file @
c431a07a
...
@@ -4,6 +4,7 @@ import (
...
@@ -4,6 +4,7 @@ import (
"net"
"net"
"net/http"
"net/http"
"net/url"
"net/url"
"os"
"path"
"path"
"strconv"
"strconv"
"strings"
"strings"
...
@@ -52,6 +53,13 @@ func NewReplacer(r *http.Request, rr *ResponseRecorder, emptyValue string) Repla
...
@@ -52,6 +53,13 @@ func NewReplacer(r *http.Request, rr *ResponseRecorder, emptyValue string) Repla
}
}
return
"http"
return
"http"
}(),
}(),
"{hostname}"
:
func
()
string
{
name
,
err
:=
os
.
Hostname
()
if
err
!=
nil
{
return
""
}
return
name
}(),
"{host}"
:
r
.
Host
,
"{host}"
:
r
.
Host
,
"{path}"
:
r
.
URL
.
Path
,
"{path}"
:
r
.
URL
.
Path
,
"{path_escaped}"
:
url
.
QueryEscape
(
r
.
URL
.
Path
),
"{path_escaped}"
:
url
.
QueryEscape
(
r
.
URL
.
Path
),
...
...
middleware/replacer_test.go
View file @
c431a07a
...
@@ -3,6 +3,7 @@ package middleware
...
@@ -3,6 +3,7 @@ package middleware
import
(
import
(
"net/http"
"net/http"
"net/http/httptest"
"net/http/httptest"
"os"
"strings"
"strings"
"testing"
"testing"
)
)
...
@@ -53,6 +54,14 @@ func TestReplace(t *testing.T) {
...
@@ -53,6 +54,14 @@ func TestReplace(t *testing.T) {
request
.
Header
.
Set
(
"ShorterVal"
,
"1"
)
request
.
Header
.
Set
(
"ShorterVal"
,
"1"
)
repl
:=
NewReplacer
(
request
,
recordRequest
,
"-"
)
repl
:=
NewReplacer
(
request
,
recordRequest
,
"-"
)
hostname
,
err
:=
os
.
Hostname
()
if
err
!=
nil
{
t
.
Fatal
(
"Failed to determine hostname
\n
"
)
}
if
expected
,
actual
:=
"This hostname is "
+
hostname
,
repl
.
Replace
(
"This hostname is {hostname}"
);
expected
!=
actual
{
t
.
Errorf
(
"{hostname} replacement: expected '%s', got '%s'"
,
expected
,
actual
)
}
if
expected
,
actual
:=
"This host is localhost."
,
repl
.
Replace
(
"This host is {host}."
);
expected
!=
actual
{
if
expected
,
actual
:=
"This host is localhost."
,
repl
.
Replace
(
"This host is {host}."
);
expected
!=
actual
{
t
.
Errorf
(
"{host} replacement: expected '%s', got '%s'"
,
expected
,
actual
)
t
.
Errorf
(
"{host} replacement: expected '%s', got '%s'"
,
expected
,
actual
)
}
}
...
...
server/server.go
View file @
c431a07a
...
@@ -14,7 +14,7 @@ import (
...
@@ -14,7 +14,7 @@ import (
"net"
"net"
"net/http"
"net/http"
"os"
"os"
"path
/filepath
"
"path"
"runtime"
"runtime"
"strings"
"strings"
"sync"
"sync"
...
@@ -336,11 +336,18 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...
@@ -336,11 +336,18 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Use URL.RawPath If you need the original, "raw" URL.Path in your middleware.
// Use URL.RawPath If you need the original, "raw" URL.Path in your middleware.
// Collapse any ./ ../ /// madness here instead of doing that in every plugin.
// Collapse any ./ ../ /// madness here instead of doing that in every plugin.
if
r
.
URL
.
Path
!=
"/"
{
if
r
.
URL
.
Path
!=
"/"
{
path
:=
filepath
.
Clean
(
r
.
URL
.
Path
)
cleanedPath
:=
path
.
Clean
(
r
.
URL
.
Path
)
if
!
strings
.
HasPrefix
(
path
,
"/"
)
{
if
cleanedPath
==
"."
{
path
=
"/"
+
path
r
.
URL
.
Path
=
"/"
}
else
{
if
!
strings
.
HasPrefix
(
cleanedPath
,
"/"
)
{
cleanedPath
=
"/"
+
cleanedPath
}
if
strings
.
HasSuffix
(
r
.
URL
.
Path
,
"/"
)
&&
!
strings
.
HasSuffix
(
cleanedPath
,
"/"
)
{
cleanedPath
=
cleanedPath
+
"/"
}
r
.
URL
.
Path
=
cleanedPath
}
}
r
.
URL
.
Path
=
path
}
}
// Execute the optional request callback if it exists and it's not disabled
// Execute the optional request callback if it exists and it's not disabled
...
@@ -438,6 +445,7 @@ func standaloneTLSTicketKeyRotation(c *tls.Config, timer *time.Ticker, exitChan
...
@@ -438,6 +445,7 @@ func standaloneTLSTicketKeyRotation(c *tls.Config, timer *time.Ticker, exitChan
c
.
SessionTicketsDisabled
=
true
// bail if we don't have the entropy for the first one
c
.
SessionTicketsDisabled
=
true
// bail if we don't have the entropy for the first one
return
return
}
}
c
.
SessionTicketKey
=
keys
[
0
]
// SetSessionTicketKeys doesn't set a 'tls.keysAlreadSet'
c
.
SetSessionTicketKeys
(
setSessionTicketKeysTestHook
(
keys
))
c
.
SetSessionTicketKeys
(
setSessionTicketKeysTestHook
(
keys
))
for
{
for
{
...
...
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