mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-29 12:16:16 +08:00
Merge branch 'master' of https://github.com/mholt/caddy
This commit is contained in:
commit
bdcbd11d65
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,3 +12,5 @@ access.log
|
||||||
|
|
||||||
/*.conf
|
/*.conf
|
||||||
Caddyfile
|
Caddyfile
|
||||||
|
|
||||||
|
og_static/
|
|
@ -43,6 +43,7 @@ By default, Caddy serves the current directory at [localhost:2015](http://localh
|
||||||
|
|
||||||
Caddy accepts some flags from the command line. Run `caddy -h` to view the help for flags. You can also pipe a Caddyfile into the caddy command.
|
Caddy accepts some flags from the command line. Run `caddy -h` to view the help for flags. You can also pipe a Caddyfile into the caddy command.
|
||||||
|
|
||||||
|
**Running as root:** We advise against this; use setcap instead, like so: `setcap cap_net_bind_service=+ep ./caddy` This will allow you to listen on ports below 1024 (like 80 and 443).
|
||||||
|
|
||||||
|
|
||||||
#### Docker Container
|
#### Docker Container
|
||||||
|
@ -51,6 +52,7 @@ Caddy is available as a Docker container from any of these sources:
|
||||||
|
|
||||||
- [abiosoft/caddy](https://registry.hub.docker.com/u/abiosoft/caddy/)
|
- [abiosoft/caddy](https://registry.hub.docker.com/u/abiosoft/caddy/)
|
||||||
- [darron/caddy](https://registry.hub.docker.com/u/darron/caddy/)
|
- [darron/caddy](https://registry.hub.docker.com/u/darron/caddy/)
|
||||||
|
- [joshix/caddy](https://registry.hub.docker.com/u/joshix/caddy/)
|
||||||
- [jumanjiman/caddy](https://registry.hub.docker.com/u/jumanjiman/caddy/)
|
- [jumanjiman/caddy](https://registry.hub.docker.com/u/jumanjiman/caddy/)
|
||||||
- [zenithar/nano-caddy](https://registry.hub.docker.com/u/zenithar/nano-caddy/)
|
- [zenithar/nano-caddy](https://registry.hub.docker.com/u/zenithar/nano-caddy/)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ const (
|
||||||
Name = "Caddy"
|
Name = "Caddy"
|
||||||
|
|
||||||
// Version is the program version
|
// Version is the program version
|
||||||
Version = "0.7.4"
|
Version = "0.7.5"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -57,6 +57,11 @@ func Load(filename string, input io.Reader) (Group, error) {
|
||||||
if config.Port == "" {
|
if config.Port == "" {
|
||||||
config.Port = Port
|
config.Port = Port
|
||||||
}
|
}
|
||||||
|
if config.Port == "http" {
|
||||||
|
config.TLS.Enabled = false
|
||||||
|
log.Printf("Warning: TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+
|
||||||
|
"specify port 80 explicitly (https://%s:80).", config.Port, config.Host, config.Host)
|
||||||
|
}
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
sharedConfig.Startup = []func() error{}
|
sharedConfig.Startup = []func() error{}
|
||||||
sharedConfig.Shutdown = []func() error{}
|
sharedConfig.Shutdown = []func() error{}
|
||||||
|
|
|
@ -31,7 +31,7 @@ func FastCGI(c *Controller) (middleware.Middleware, error) {
|
||||||
SoftwareName: c.AppName,
|
SoftwareName: c.AppName,
|
||||||
SoftwareVersion: c.AppVersion,
|
SoftwareVersion: c.AppVersion,
|
||||||
ServerName: c.Host,
|
ServerName: c.Host,
|
||||||
ServerPort: c.Port,
|
ServerPort: c.Port, // BUG: This is not known until the server blocks are split up...
|
||||||
}
|
}
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"log"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
|
@ -10,11 +9,6 @@ import (
|
||||||
|
|
||||||
func TLS(c *Controller) (middleware.Middleware, error) {
|
func TLS(c *Controller) (middleware.Middleware, error) {
|
||||||
c.TLS.Enabled = true
|
c.TLS.Enabled = true
|
||||||
if c.Port == "http" {
|
|
||||||
c.TLS.Enabled = false
|
|
||||||
log.Printf("Warning: TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+
|
|
||||||
"specify port 80 explicitly (https://%s:80).", c.Port, c.Host, c.Host)
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
|
|
7
dist/CHANGES.txt
vendored
7
dist/CHANGES.txt
vendored
|
@ -1,9 +1,14 @@
|
||||||
CHANGES
|
CHANGES
|
||||||
|
|
||||||
<master>
|
0.7.5 (August 5, 2015)
|
||||||
|
- core: All listeners bind to 0.0.0.0 unless 'bind' directive is used
|
||||||
- fastcgi: Set HTTPS env variable if connection is secure
|
- fastcgi: Set HTTPS env variable if connection is secure
|
||||||
|
- log: Output to system log (except Windows)
|
||||||
|
- markdown: Added dev command to disable caching during development
|
||||||
- markdown: Fixed error reporting during initial site generation
|
- markdown: Fixed error reporting during initial site generation
|
||||||
- markdown: Fixed crash if path does not exist when server starts
|
- markdown: Fixed crash if path does not exist when server starts
|
||||||
|
- markdown: Fixed site generation and link indexing when files change
|
||||||
|
- templates: Added .NowDate for use in date-related functions
|
||||||
- Several bug fixes related to startup and shutdown functions
|
- Several bug fixes related to startup and shutdown functions
|
||||||
|
|
||||||
|
|
||||||
|
|
2
dist/README.txt
vendored
2
dist/README.txt
vendored
|
@ -1,4 +1,4 @@
|
||||||
CADDY 0.7.4
|
CADDY 0.7.5
|
||||||
|
|
||||||
Website
|
Website
|
||||||
https://caddyserver.com
|
https://caddyserver.com
|
||||||
|
|
|
@ -4,12 +4,14 @@ package browse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
@ -185,7 +187,6 @@ func directoryListing(files []os.FileInfo, r *http.Request, canGoUp bool, root s
|
||||||
// ServeHTTP implements the middleware.Handler interface.
|
// ServeHTTP implements the middleware.Handler interface.
|
||||||
func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
filename := b.Root + r.URL.Path
|
filename := b.Root + r.URL.Path
|
||||||
|
|
||||||
info, err := os.Stat(filename)
|
info, err := os.Stat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return b.Next.ServeHTTP(w, r)
|
return b.Next.ServeHTTP(w, r)
|
||||||
|
@ -264,12 +265,47 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
listing.applySort()
|
listing.applySort()
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = bc.Template.Execute(&buf, listing)
|
// check if we should provide json
|
||||||
if err != nil {
|
acceptHeader := strings.Join(r.Header["Accept"], ",")
|
||||||
return http.StatusInternalServerError, err
|
if strings.Contains(strings.ToLower(acceptHeader), "application/json") {
|
||||||
|
var marsh []byte
|
||||||
|
// check if we are limited
|
||||||
|
if limitQuery := r.URL.Query().Get("limit"); limitQuery != "" {
|
||||||
|
limit, err := strconv.Atoi(limitQuery)
|
||||||
|
if err != nil { // if the 'limit' query can't be interpreted as a number, return err
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
}
|
||||||
|
// if `limit` is equal or less than len(listing.Items) and bigger than 0, list them
|
||||||
|
if limit <= len(listing.Items) && limit > 0 {
|
||||||
|
marsh, err = json.Marshal(listing.Items[:limit])
|
||||||
|
} else { // if the 'limit' query is empty, or has the wrong value, list everything
|
||||||
|
marsh, err = json.Marshal(listing.Items)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
} else { // there's no 'limit' query, list them all
|
||||||
|
marsh, err = json.Marshal(listing.Items)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the marshaled json to buf
|
||||||
|
if _, err = buf.Write(marsh); err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
} else { // there's no 'application/json' in the 'Accept' header, browse normally
|
||||||
|
err = bc.Template.Execute(&buf, listing)
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
buf.WriteTo(w)
|
buf.WriteTo(w)
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
package browse
|
package browse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// "sort" package has "IsSorted" function, but no "IsReversed";
|
// "sort" package has "IsSorted" function, but no "IsReversed";
|
||||||
|
@ -154,4 +156,88 @@ func TestBrowseTemplate(t *testing.T) {
|
||||||
if respBody != expectedBody {
|
if respBody != expectedBody {
|
||||||
t.Fatalf("Expected body: %v got: %v", expectedBody, respBody)
|
t.Fatalf("Expected body: %v got: %v", expectedBody, respBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrowseJson(t *testing.T) {
|
||||||
|
|
||||||
|
b := Browse{
|
||||||
|
Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
t.Fatalf("Next shouldn't be called")
|
||||||
|
return 0, nil
|
||||||
|
}),
|
||||||
|
Root: "./testdata",
|
||||||
|
Configs: []Config{
|
||||||
|
Config{
|
||||||
|
PathScope: "/photos",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/photos/", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test: Could not create HTTP request: %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
b.ServeHTTP(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Wrong status, expected %d, got %d", http.StatusOK, rec.Code)
|
||||||
|
}
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
actualJsonResponseString := rec.Body.String()
|
||||||
|
|
||||||
|
//generating the listing to compare it with the response body
|
||||||
|
file, err := os.Open(b.Root + req.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
t.Fatalf("Os Permission Error")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
files, err := file.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to Read Contents of the directory")
|
||||||
|
}
|
||||||
|
var fileinfos []FileInfo
|
||||||
|
for _, f := range files {
|
||||||
|
name := f.Name()
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
name += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
url := url.URL{Path: name}
|
||||||
|
|
||||||
|
fileinfos = append(fileinfos, FileInfo{
|
||||||
|
IsDir: f.IsDir(),
|
||||||
|
Name: f.Name(),
|
||||||
|
Size: f.Size(),
|
||||||
|
URL: url.String(),
|
||||||
|
ModTime: f.ModTime(),
|
||||||
|
Mode: f.Mode(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
listing := Listing{
|
||||||
|
Items: fileinfos,
|
||||||
|
}
|
||||||
|
listing.Sort = "name"
|
||||||
|
listing.Order = "asc"
|
||||||
|
listing.applySort()
|
||||||
|
|
||||||
|
marsh, err := json.Marshal(listing.Items)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to Marshal the listing ")
|
||||||
|
}
|
||||||
|
expectedJsonString := string(marsh)
|
||||||
|
if actualJsonResponseString != expectedJsonString {
|
||||||
|
t.Errorf("Json response string doesnt match the expected Json response ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
41
middleware/middleware_test.go
Normal file
41
middleware/middleware_test.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIndexfile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
rootDir http.FileSystem
|
||||||
|
fpath string
|
||||||
|
indexFiles []string
|
||||||
|
shouldErr bool
|
||||||
|
expectedFilePath string //retun value
|
||||||
|
expectedBoolValue bool //return value
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
http.Dir("./templates/testdata"), "/images/", []string{"img.htm"},
|
||||||
|
false,
|
||||||
|
"/images/img.htm", true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
actualFilePath, actualBoolValue := IndexFile(test.rootDir, test.fpath, test.indexFiles)
|
||||||
|
if actualBoolValue == true && test.shouldErr {
|
||||||
|
t.Errorf("Test %d didn't error, but it should have", i)
|
||||||
|
} else if actualBoolValue != true && !test.shouldErr {
|
||||||
|
t.Errorf("Test %d errored, but it shouldn't have; got %s", i, "Please Add a / at the end of fpath or the indexFiles doesnt exist")
|
||||||
|
}
|
||||||
|
if actualFilePath != test.expectedFilePath {
|
||||||
|
t.Fatalf("Test %d expected returned filepath to be %s, but got %s ",
|
||||||
|
i, test.expectedFilePath, actualFilePath)
|
||||||
|
|
||||||
|
}
|
||||||
|
if actualBoolValue != test.expectedBoolValue {
|
||||||
|
t.Fatalf("Test %d expected returned bool value to be %v, but got %v ",
|
||||||
|
i, test.expectedBoolValue, actualBoolValue)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
middleware/recorder_test.go
Normal file
40
middleware/recorder_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewResponseRecorder(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
recordRequest := NewResponseRecorder(w)
|
||||||
|
if !(recordRequest.ResponseWriter == w) {
|
||||||
|
t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n")
|
||||||
|
}
|
||||||
|
if recordRequest.status != http.StatusOK {
|
||||||
|
t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", recordRequest.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestWriteHeader(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
recordRequest := NewResponseRecorder(w)
|
||||||
|
recordRequest.WriteHeader(401)
|
||||||
|
if w.Code != 401 || recordRequest.status != 401 {
|
||||||
|
t.Fatalf("Expected Response status to be set to 401, but found %d\n", recordRequest.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
responseTestString := "test"
|
||||||
|
recordRequest := NewResponseRecorder(w)
|
||||||
|
buf := []byte(responseTestString)
|
||||||
|
recordRequest.Write(buf)
|
||||||
|
if recordRequest.size != len(buf) {
|
||||||
|
t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size)
|
||||||
|
}
|
||||||
|
if w.Body.String() != responseTestString {
|
||||||
|
t.Fatalf("Expected Response Body to be %s , but found %s\n", w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
38
middleware/replacer_test.go
Normal file
38
middleware/replacer_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewReplacer(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
recordRequest := NewResponseRecorder(w)
|
||||||
|
userJson := `{"username": "dennis"}`
|
||||||
|
|
||||||
|
reader := strings.NewReader(userJson) //Convert string to reader
|
||||||
|
|
||||||
|
request, err := http.NewRequest("POST", "http://caddyserver.com", reader) //Create request with JSON body
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Request Formation Failed \n")
|
||||||
|
}
|
||||||
|
replaceValues := NewReplacer(request, recordRequest, "")
|
||||||
|
|
||||||
|
switch v := replaceValues.(type) {
|
||||||
|
case replacer:
|
||||||
|
if v.replacements["{host}"] != "caddyserver.com" {
|
||||||
|
t.Errorf("Expected host to be caddyserver.com")
|
||||||
|
}
|
||||||
|
if v.replacements["{method}"] != "POST" {
|
||||||
|
t.Errorf("Expected request method to be POST")
|
||||||
|
}
|
||||||
|
if v.replacements["{status}"] != "200" {
|
||||||
|
t.Errorf("Expected status to be 200")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Fatalf("Return Value from New Replacer expected pass type assertion into a replacer type \n")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user