mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
a bit linting and some tests are online again
Former-commit-id: bd5463a169a36b078dba1c1b6e7dd3ffbd627617
This commit is contained in:
parent
f747c682b9
commit
c4f5fae561
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
var (
|
||||
// Prefix the error prefix, applies to each error's message.
|
||||
// Should not be changed.
|
||||
Prefix = ""
|
||||
// NewLine adds a new line to the end of each error's message
|
||||
// defaults to true
|
||||
|
|
98
core/errors/errors_test.go
Normal file
98
core/errors/errors_test.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
// black-box testing
|
||||
package errors_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
var errMessage = "User with mail: %s already exists"
|
||||
var errUserAlreadyExists = errors.New(errMessage)
|
||||
var userMail = "user1@mail.go"
|
||||
var expectedUserAlreadyExists = "User with mail: user1@mail.go already exists"
|
||||
|
||||
func getNewLine() string {
|
||||
if errors.NewLine {
|
||||
return "\n"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ExampleError() {
|
||||
fmt.Print(errUserAlreadyExists.Format(userMail))
|
||||
// first output first Output line
|
||||
fmt.Print(errUserAlreadyExists.Format(userMail).Append("Please change your mail addr"))
|
||||
// second output second and third Output lines
|
||||
|
||||
// Output:
|
||||
// User with mail: user1@mail.go already exists
|
||||
// User with mail: user1@mail.go already exists
|
||||
// Please change your mail addr
|
||||
}
|
||||
|
||||
func do(method string, testErr *errors.Error, expectingMsg string, t *testing.T) {
|
||||
formattedErr := func() error {
|
||||
return testErr.Format(userMail)
|
||||
}()
|
||||
|
||||
if formattedErr.Error() != expectingMsg {
|
||||
t.Fatalf("error %s failed, expected:\n%s got:\n%s", method, expectingMsg, formattedErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
expected := errors.Prefix + expectedUserAlreadyExists + getNewLine()
|
||||
do("Format Test", errUserAlreadyExists, expected, t)
|
||||
}
|
||||
|
||||
func TestAppendErr(t *testing.T) {
|
||||
errors.NewLine = true
|
||||
errors.Prefix = "error: "
|
||||
|
||||
errChangeMailMsg := "Please change your mail addr"
|
||||
errChangeMail := fmt.Errorf(errChangeMailMsg) // test go standard error
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + errChangeMailMsg + getNewLine() // first Prefix and last newline lives inside do
|
||||
errAppended := errUserAlreadyExists.AppendErr(errChangeMail)
|
||||
do("Append Test Standard error type", &errAppended, expectedErrorMessage, t)
|
||||
}
|
||||
|
||||
func TestAppendError(t *testing.T) {
|
||||
errors.NewLine = true
|
||||
errors.Prefix = "error: "
|
||||
|
||||
errChangeMailMsg := "Please change your mail addr"
|
||||
errChangeMail := errors.New(errChangeMailMsg) // test Error struct
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + errChangeMail.Error() + getNewLine() // first Prefix and last newline lives inside do
|
||||
errAppended := errUserAlreadyExists.AppendErr(errChangeMail)
|
||||
do("Append Test Error type", &errAppended, expectedErrorMessage, t)
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
errors.NewLine = true
|
||||
errors.Prefix = "error: "
|
||||
|
||||
errChangeMailMsg := "Please change your mail addr"
|
||||
expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + errChangeMailMsg + getNewLine() // first Prefix and last newline lives inside do
|
||||
errAppended := errUserAlreadyExists.Append(errChangeMailMsg)
|
||||
do("Append Test string Message", &errAppended, expectedErrorMessage, t)
|
||||
}
|
||||
|
||||
func TestNewLine(t *testing.T) {
|
||||
errors.NewLine = false
|
||||
|
||||
errNoNewLine := errors.New(errMessage)
|
||||
expected := errors.Prefix + expectedUserAlreadyExists
|
||||
do("NewLine Test", errNoNewLine, expected, t)
|
||||
|
||||
errors.NewLine = true
|
||||
}
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
errors.Prefix = "MyPrefix: "
|
||||
|
||||
errUpdatedPrefix := errors.New(errMessage)
|
||||
expected := errors.Prefix + expectedUserAlreadyExists + "\n"
|
||||
do("Prefix Test with "+errors.Prefix, errUpdatedPrefix, expected, t)
|
||||
}
|
66
core/handlerconv/from_std_test.go
Normal file
66
core/handlerconv/from_std_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
// black-box testing
|
||||
package handlerconv_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/handlerconv"
|
||||
"github.com/kataras/iris/httptest"
|
||||
)
|
||||
|
||||
func TestFromStd(t *testing.T) {
|
||||
expected := "ok"
|
||||
std := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(expected))
|
||||
}
|
||||
|
||||
h := handlerconv.FromStd(http.HandlerFunc(std))
|
||||
|
||||
hFunc := handlerconv.FromStd(std)
|
||||
|
||||
app := iris.New()
|
||||
app.Get("/handler", h)
|
||||
app.Get("/func", hFunc)
|
||||
|
||||
e := httptest.New(app, t)
|
||||
|
||||
e.GET("/handler").
|
||||
Expect().Status(iris.StatusOK).Body().Equal(expected)
|
||||
|
||||
e.GET("/func").
|
||||
Expect().Status(iris.StatusOK).Body().Equal(expected)
|
||||
}
|
||||
|
||||
func TestFromStdWithNext(t *testing.T) {
|
||||
|
||||
basicauth := "secret"
|
||||
passed := "ok"
|
||||
|
||||
stdWNext := func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if username, password, ok := r.BasicAuth(); ok &&
|
||||
username == basicauth && password == basicauth {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(iris.StatusForbidden)
|
||||
}
|
||||
|
||||
h := handlerconv.FromStdWithNext(stdWNext)
|
||||
next := func(ctx context.Context) {
|
||||
ctx.WriteString(passed)
|
||||
}
|
||||
|
||||
app := iris.New()
|
||||
app.Get("/handlerwithnext", h, next)
|
||||
|
||||
e := httptest.New(app, t)
|
||||
|
||||
e.GET("/handlerwithnext").
|
||||
Expect().Status(iris.StatusForbidden)
|
||||
|
||||
e.GET("/handlerwithnext").WithBasicAuth(basicauth, basicauth).
|
||||
Expect().Status(iris.StatusOK).Body().Equal(passed)
|
||||
}
|
|
@ -5,10 +5,13 @@
|
|||
package host
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
)
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
|
@ -29,7 +32,6 @@ func singleJoiningSlash(a, b string) string {
|
|||
// the target request will be for /base/dir.
|
||||
//
|
||||
// Relative to httputil.NewSingleHostReverseProxy with some additions.
|
||||
// Used for the deprecated `LETSENCRYPT`.
|
||||
func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
|
||||
targetQuery := target.RawQuery
|
||||
director := func(req *http.Request) {
|
||||
|
@ -42,8 +44,22 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
|
|||
} else {
|
||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
|
||||
if _, ok := req.Header["User-Agent"]; !ok {
|
||||
// explicitly disable User-Agent so it's not set to default value
|
||||
req.Header.Set("User-Agent", "")
|
||||
}
|
||||
return &httputil.ReverseProxy{Director: director}
|
||||
}
|
||||
p := &httputil.ReverseProxy{Director: director}
|
||||
|
||||
if nettools.IsLoopbackHost(target.Host) {
|
||||
transport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
p.Transport = transport
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// NewProxy returns a new host (server supervisor) which
|
||||
|
@ -56,7 +72,6 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy {
|
|||
// proxy.ListenAndServe() // use of proxy.Shutdown to close the proxy server.
|
||||
func NewProxy(hostAddr string, target *url.URL) *Supervisor {
|
||||
proxyHandler := ProxyHandler(target)
|
||||
|
||||
proxy := New(&http.Server{
|
||||
Addr: hostAddr,
|
||||
Handler: proxyHandler,
|
||||
|
|
60
core/host/proxy_test.go
Normal file
60
core/host/proxy_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
// black-box testing
|
||||
package host_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
"github.com/kataras/iris/context"
|
||||
"github.com/kataras/iris/core/host"
|
||||
"github.com/kataras/iris/httptest"
|
||||
)
|
||||
|
||||
func TestProxy(t *testing.T) {
|
||||
expectedIndex := "ok /"
|
||||
expectedAbout := "ok /about"
|
||||
unexpectedRoute := "unexpected"
|
||||
|
||||
// proxySrv := iris.New()
|
||||
u, err := url.Parse("https://localhost")
|
||||
if err != nil {
|
||||
t.Fatalf("%v while parsing url", err)
|
||||
}
|
||||
|
||||
// p := host.ProxyHandler(u)
|
||||
// transport := &http.Transport{
|
||||
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
// }
|
||||
// p.Transport = transport
|
||||
// proxySrv.Downgrade(p.ServeHTTP)
|
||||
// go proxySrv.Run(iris.Addr(":80"), iris.WithoutBanner, iris.WithoutInterruptHandler)
|
||||
|
||||
go host.NewProxy(":80", u).ListenAndServe()
|
||||
|
||||
app := iris.New()
|
||||
app.Get("/", func(ctx context.Context) {
|
||||
ctx.WriteString(expectedIndex)
|
||||
})
|
||||
|
||||
app.Get("/about", func(ctx context.Context) {
|
||||
ctx.WriteString(expectedAbout)
|
||||
})
|
||||
|
||||
app.OnErrorCode(iris.StatusNotFound, func(ctx context.Context) {
|
||||
ctx.WriteString(unexpectedRoute)
|
||||
})
|
||||
|
||||
l, err := net.Listen("tcp", "localhost:443")
|
||||
if err != nil {
|
||||
t.Fatalf("%v while creating tcp4 listener for new tls local test listener", err)
|
||||
}
|
||||
// main server
|
||||
go app.Run(iris.Listener(httptest.NewLocalTLSListener(l)), iris.WithoutBanner)
|
||||
|
||||
e := httptest.NewInsecure(t, httptest.URL("http://localhost"))
|
||||
e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedIndex)
|
||||
e.GET("/about").Expect().Status(iris.StatusOK).Body().Equal(expectedAbout)
|
||||
e.GET("/notfound").Expect().Status(iris.StatusNotFound).Body().Equal(unexpectedRoute)
|
||||
}
|
|
@ -22,13 +22,23 @@ func (t *task) isCanceled() bool {
|
|||
return atomic.LoadInt32(&t.alreadyCanceled) != 0
|
||||
}
|
||||
|
||||
// Scheduler is a type of an event emmiter.
|
||||
// Can register a specific task for a specific event
|
||||
// when host is starting the server or host is interrupted by CTRL+C/CMD+C.
|
||||
// It's being used internally on host supervisor.
|
||||
type Scheduler struct {
|
||||
onServeTasks []*task
|
||||
onInterruptTasks []*task
|
||||
}
|
||||
|
||||
// TaskCancelFunc cancels a Task when called.
|
||||
type TaskCancelFunc func()
|
||||
|
||||
// Schedule schedule/registers a Task,
|
||||
// it will be executed/run to when host starts the server
|
||||
// or when host is interrupted by CTRL+C/CMD+C based on the TaskRunner type.
|
||||
//
|
||||
// See `OnInterrupt` and `ScheduleFunc` too.
|
||||
func (s *Scheduler) Schedule(runner TaskRunner) TaskCancelFunc {
|
||||
|
||||
t := new(task)
|
||||
|
@ -50,6 +60,11 @@ func (s *Scheduler) Schedule(runner TaskRunner) TaskCancelFunc {
|
|||
}
|
||||
}
|
||||
|
||||
// ScheduleFunc schedule/registers a task function,
|
||||
// it will be executed/run to when host starts the server
|
||||
// or when host is interrupted by CTRL+C/CMD+C based on the TaskRunner type.
|
||||
//
|
||||
// See `OnInterrupt` and `ScheduleFunc` too.
|
||||
func (s *Scheduler) ScheduleFunc(runner func(TaskProcess)) TaskCancelFunc {
|
||||
return s.Schedule(TaskRunnerFunc(runner))
|
||||
}
|
||||
|
@ -63,10 +78,14 @@ func cancelTasks(tasks []*task) {
|
|||
}
|
||||
}
|
||||
|
||||
// CancelOnServeTasks cancels all tasks that are scheduled to run when
|
||||
// host is starting the server, when the server is alive and online.
|
||||
func (s *Scheduler) CancelOnServeTasks() {
|
||||
cancelTasks(s.onServeTasks)
|
||||
}
|
||||
|
||||
// CancelOnInterruptTasks cancels all tasks that are scheduled to run when
|
||||
// host is being interrupted by CTRL+C/CMD+C, when the server is alive and online as well.
|
||||
func (s *Scheduler) CancelOnInterruptTasks() {
|
||||
cancelTasks(s.onInterruptTasks)
|
||||
}
|
||||
|
@ -124,6 +143,8 @@ func (s *Scheduler) notifyErr(err error) {
|
|||
})
|
||||
}
|
||||
|
||||
// CopyTo copies all tasks from "s" to "to" Scheduler.
|
||||
// It doesn't care about anything else.
|
||||
func (s *Scheduler) CopyTo(to *Scheduler) {
|
||||
s.visit(func(t *task) {
|
||||
rnner := t.runner
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// white-box testing
|
||||
package host
|
||||
|
||||
import (
|
||||
|
|
|
@ -36,6 +36,13 @@ type Supervisor struct {
|
|||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// New returns a new host supervisor
|
||||
// based on a native net/http "srv".
|
||||
//
|
||||
// It contains all native net/http's Server methods.
|
||||
// Plus you can add tasks on specific events.
|
||||
// It has its own flow, which means that you can prevent
|
||||
// to return and exit and restore the flow too.
|
||||
func New(srv *http.Server) *Supervisor {
|
||||
return &Supervisor{
|
||||
server: srv,
|
||||
|
@ -45,10 +52,23 @@ func New(srv *http.Server) *Supervisor {
|
|||
}
|
||||
}
|
||||
|
||||
// DeferFlow defers the flow of the exeuction,
|
||||
// i.e: when server should return error and exit
|
||||
// from app, a DeferFlow call inside a Task
|
||||
// can wait for a `RestoreFlow` to exit or not exit if
|
||||
// host's server is "fixed".
|
||||
//
|
||||
// See `RestoreFlow` too.
|
||||
func (su *Supervisor) DeferFlow() {
|
||||
atomic.StoreInt32(&su.shouldWait, 1)
|
||||
}
|
||||
|
||||
// RestoreFlow restores the flow of the execution,
|
||||
// if called without a `DeferFlow` call before
|
||||
// then it does nothing.
|
||||
// See tests to understand how that can be useful on specific cases.
|
||||
//
|
||||
// See `DeferFlow` too.
|
||||
func (su *Supervisor) RestoreFlow() {
|
||||
if su.isWaiting() {
|
||||
atomic.StoreInt32(&su.shouldWait, 0)
|
||||
|
@ -162,10 +182,25 @@ func (su *Supervisor) newListener() (net.Listener, error) {
|
|||
return l, nil
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the Listener l, creating a
|
||||
// new service goroutine for each. The service goroutines read requests and
|
||||
// then call su.server.Handler to reply to them.
|
||||
//
|
||||
// For HTTP/2 support, server.TLSConfig should be initialized to the
|
||||
// provided listener's TLS Config before calling Serve. If
|
||||
// server.TLSConfig is non-nil and doesn't include the string "h2" in
|
||||
// Config.NextProtos, HTTP/2 support is not enabled.
|
||||
//
|
||||
// Serve always returns a non-nil error. After Shutdown or Close, the
|
||||
// returned error is http.ErrServerClosed.
|
||||
func (su *Supervisor) Serve(l net.Listener) error {
|
||||
return su.supervise(func() error { return su.server.Serve(l) })
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address addr
|
||||
// and then calls Serve with handler to handle requests
|
||||
// on incoming connections.
|
||||
// Accepted connections are configured to enable TCP keep-alives.
|
||||
func (su *Supervisor) ListenAndServe() error {
|
||||
l, err := su.newListener()
|
||||
if err != nil {
|
||||
|
@ -178,6 +213,11 @@ func setupHTTP2(cfg *tls.Config) {
|
|||
cfg.NextProtos = append(cfg.NextProtos, "h2") // HTTP2
|
||||
}
|
||||
|
||||
// ListenAndServeTLS acts identically to ListenAndServe, except that it
|
||||
// expects HTTPS connections. Additionally, files containing a certificate and
|
||||
// matching private key for the server must be provided. If the certificate
|
||||
// is signed by a certificate authority, the certFile should be the concatenation
|
||||
// of the server's certificate, any intermediates, and the CA's certificate.
|
||||
func (su *Supervisor) ListenAndServeTLS(certFile string, keyFile string) error {
|
||||
if certFile == "" || keyFile == "" {
|
||||
return errors.New("certFile or keyFile missing")
|
||||
|
@ -195,6 +235,9 @@ func (su *Supervisor) ListenAndServeTLS(certFile string, keyFile string) error {
|
|||
return su.ListenAndServe()
|
||||
}
|
||||
|
||||
// ListenAndServeAutoTLS acts identically to ListenAndServe, except that it
|
||||
// expects HTTPS connections. server's certificates are auto generated from LETSENCRYPT using
|
||||
// the golang/x/net/autocert package.
|
||||
func (su *Supervisor) ListenAndServeAutoTLS() error {
|
||||
autoTLSManager := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// white-box testing
|
||||
|
||||
package host
|
||||
|
||||
|
|
|
@ -12,16 +12,21 @@ import (
|
|||
"context"
|
||||
"github.com/kataras/iris/core/nettools"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type (
|
||||
// FlowController exports the `DeferFlow`
|
||||
// and `RestoreFlow` capabilities.
|
||||
// Read more at Supervisor.
|
||||
FlowController interface {
|
||||
DeferFlow()
|
||||
RestoreFlow()
|
||||
}
|
||||
)
|
||||
|
||||
// TaskHost contains all the necessary information
|
||||
// about the host supervisor, its server
|
||||
// and the exports the whole flow controller of it.
|
||||
type TaskHost struct {
|
||||
su *Supervisor
|
||||
// Supervisor with access fields when server is running, i.e restrict access to "Schedule"
|
||||
|
@ -35,14 +40,17 @@ type TaskHost struct {
|
|||
errChan chan error
|
||||
}
|
||||
|
||||
// Done filled when server was shutdown.
|
||||
func (h TaskHost) Done() <-chan struct{} {
|
||||
return h.doneChan
|
||||
}
|
||||
|
||||
// Err filled when server received an error.
|
||||
func (h TaskHost) Err() <-chan error {
|
||||
return h.errChan
|
||||
}
|
||||
|
||||
// Serve can (re)run the server with the latest known configuration.
|
||||
func (h TaskHost) Serve() error {
|
||||
// the underline server's serve, using the "latest known" listener from the supervisor.
|
||||
l, err := h.su.newListener()
|
||||
|
@ -69,24 +77,40 @@ func (h TaskHost) Hostname() string {
|
|||
return nettools.ResolveHostname(h.su.server.Addr)
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the server without interrupting any
|
||||
// active connections. Shutdown works by first closing all open
|
||||
// listeners, then closing all idle connections, and then waiting
|
||||
// indefinitely for connections to return to idle and then shut down.
|
||||
// If the provided context expires before the shutdown is complete,
|
||||
// then the context's error is returned.
|
||||
//
|
||||
// Shutdown does not attempt to close nor wait for hijacked
|
||||
// connections such as WebSockets. The caller of Shutdown should
|
||||
// separately notify such long-lived connections of shutdown and wait
|
||||
// for them to close, if desired.
|
||||
func (h TaskHost) Shutdown(ctx context.Context) error {
|
||||
// the underline server's Shutdown (otherwise we will cancel all tasks and do cycles)
|
||||
return h.su.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (h TaskHost) PID() int {
|
||||
return h.pid
|
||||
}
|
||||
|
||||
// TaskProcess is the context of the Task runner.
|
||||
// Contains the host's information and actions
|
||||
// and its self cancelation emmiter.
|
||||
type TaskProcess struct {
|
||||
canceledChan chan struct{}
|
||||
host TaskHost
|
||||
}
|
||||
|
||||
// Done filled when this task is canceled.
|
||||
func (p TaskProcess) Done() <-chan struct{} {
|
||||
return p.canceledChan
|
||||
}
|
||||
|
||||
// Host returns the TaskHost.
|
||||
//
|
||||
// TaskHost contains all the necessary information
|
||||
// about the host supervisor, its server
|
||||
// and the exports the whole flow controller of it.
|
||||
func (p TaskProcess) Host() TaskHost {
|
||||
return p.host
|
||||
}
|
||||
|
@ -97,7 +121,6 @@ func createTaskHost(su *Supervisor) TaskHost {
|
|||
FlowController: su,
|
||||
doneChan: make(chan struct{}),
|
||||
errChan: make(chan error),
|
||||
pid: os.Getpid(),
|
||||
}
|
||||
|
||||
return host
|
||||
|
@ -121,11 +144,21 @@ func newTaskProcess(host TaskHost) TaskProcess {
|
|||
// A Task is considered to be a lightweight process because it runs within the context of a Supervisor
|
||||
// and takes advantage of resources allocated for that Supervisor and its Server.
|
||||
type TaskRunner interface {
|
||||
// Run runs the task based on its TaskProcess which contains
|
||||
// all the necessary information and actions to control the host supervisor
|
||||
// and its server.
|
||||
Run(TaskProcess)
|
||||
}
|
||||
|
||||
// TaskRunnerFunc "converts" a func(TaskProcess) to a complete TaskRunner.
|
||||
// Its functionality is exactly the same as TaskRunner.
|
||||
//
|
||||
// See `TaskRunner` too.
|
||||
type TaskRunnerFunc func(TaskProcess)
|
||||
|
||||
// Run runs the task based on its TaskProcess which contains
|
||||
// all the necessary information and actions to control the host supervisor
|
||||
// and its server.
|
||||
func (s TaskRunnerFunc) Run(proc TaskProcess) {
|
||||
s(proc)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ import (
|
|||
"runtime"
|
||||
)
|
||||
|
||||
// WriteBannerTask is a task which accepts a logger(io.Writer)
|
||||
// and a "banner" text to write to following
|
||||
// by a generated message based on the host supervisor's server and writes it to the "w".
|
||||
// This task runs on serve.
|
||||
func WriteBannerTask(w io.Writer, banner string) TaskRunnerFunc {
|
||||
return func(proc TaskProcess) {
|
||||
listeningURI := proc.Host().HostURL()
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// white-box testing
|
||||
package host
|
||||
|
||||
import (
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
// value(Task) when an OS interrupt/kill signal received.
|
||||
type OnInterrupt TaskRunnerFunc
|
||||
|
||||
// Run runs the interrupt task and completes the TaskRunner interface.
|
||||
func (t OnInterrupt) Run(proc TaskProcess) {
|
||||
t(proc)
|
||||
}
|
||||
|
|
18
core/logger/logger_test.go
Normal file
18
core/logger/logger_test.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// black-box testing
|
||||
package logger_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/kataras/iris/core/logger"
|
||||
)
|
||||
|
||||
func TestLog(t *testing.T) {
|
||||
msg := "Hello this is me"
|
||||
l := &bytes.Buffer{}
|
||||
logger.Log(l, msg)
|
||||
if expected, got := msg, l.String(); expected != got {
|
||||
t.Fatalf("expected %s but got %s", expected, got)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
// white-box testing
|
||||
package memstore
|
||||
|
||||
import (
|
||||
|
@ -91,3 +92,21 @@ func TestImmutable(t *testing.T) {
|
|||
t.Fatalf("expected objp to be immutable but caller was able to change its value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestImmutableSetOnlyWithSetImmutable(t *testing.T) {
|
||||
var p Store
|
||||
|
||||
p.SetImmutable("objp", &myTestObject{"value"})
|
||||
|
||||
p.Set("objp", &myTestObject{"modified"})
|
||||
vObjP := p.Get("objp").(myTestObject)
|
||||
if vObjP.name == "modified" {
|
||||
t.Fatalf("caller should not be able to change the immutable entry with a simple `Set`")
|
||||
}
|
||||
|
||||
p.SetImmutable("objp", &myTestObject{"value with SetImmutable"})
|
||||
vvObjP := p.Get("objp").(myTestObject)
|
||||
if vvObjP.name != "value with SetImmutable" {
|
||||
t.Fatalf("caller should be able to change the immutable entry with a `SetImmutable`")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,10 @@ var IsLoopbackHost = func(requestHost string) bool {
|
|||
// would probably not want to reach the server with different Application.Config.Addr than
|
||||
// he/she declared.
|
||||
portOrPathIdx := strings.LastIndexByte(requestHost, ':')
|
||||
|
||||
if portOrPathIdx == 0 { // 0.0.0.0:[...]/localhost:[...]/127.0.0.1:[...]/ipv6 local...
|
||||
return true
|
||||
}
|
||||
// this will not catch ipv6 loopbacks like subdomain.0000:0:0000::01.1:8080
|
||||
// but, again, is for developers only, is hard to try to navigate with something like this,
|
||||
// and if that happened, I provide a way to override the whole "algorithm" to a custom one via "IsLoopbackHost".
|
||||
|
|
|
@ -91,6 +91,11 @@ func (router *Router) Downgrade(newMainHandler http.HandlerFunc) {
|
|||
router.mu.Unlock()
|
||||
}
|
||||
|
||||
// Downgraded returns true if this router is downgraded.
|
||||
func (router *Router) Downgraded() bool {
|
||||
return router.mainHandler != nil && router.requestHandler == nil
|
||||
}
|
||||
|
||||
// WrapRouter adds a wrapper on the top of the main router.
|
||||
// Usually it's useful for third-party middleware
|
||||
// when need to wrap the entire application with a middleware like CORS.
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/iris-contrib/httpexpect"
|
||||
|
||||
"github.com/kataras/iris"
|
||||
)
|
||||
|
||||
|
|
97
httptest/netutils.go
Normal file
97
httptest/netutils.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package httptest
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
// copied from net/http/httptest/internal
|
||||
|
||||
// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
|
||||
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
|
||||
// generated from src/crypto/tls:
|
||||
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
// note: these are not the net/http/httptest/internal contents but doesn't matter.
|
||||
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDAzCCAeugAwIBAgIJAP0pWSuIYyQCMA0GCSqGSIb3DQEBBQUAMBgxFjAUBgNV
|
||||
BAMMDWxvY2FsaG9zdDozMzEwHhcNMTYxMjI1MDk1OTI3WhcNMjYxMjIzMDk1OTI3
|
||||
WjAYMRYwFAYDVQQDDA1sb2NhbGhvc3Q6MzMxMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
|
||||
mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
|
||||
tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
|
||||
3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
|
||||
sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
|
||||
PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||
MXrBvbILQmiwjUj19aecF2N+6IkwHwYDVR0jBBgwFoAUMXrBvbILQmiwjUj19aec
|
||||
F2N+6IkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA4zbFml1t9KXJ
|
||||
OijAV8gALePR8v04DQwJP+jsRxXw5zzhc8Wqzdd2hjUd07mfRWAvmyywrmhCV6zq
|
||||
OHznR+aqIqHtm0vV8OpKxLoIQXavfBd6axEXt3859RDM4xJNwIlxs3+LWGPgINud
|
||||
wjJqjyzSlhJpQpx4YZ5Da+VMiqAp8N1UeaZ5lBvmSDvoGh6HLODSqtPlWMrldRW9
|
||||
AfsXVxenq81MIMeKW2fSOoPnWZ4Vjf1+dSlbJE/DD4zzcfbyfgY6Ep/RrUltJ3ag
|
||||
FQbuNTQlgKabe21dSL9zJ2PengVKXl4Trl+4t/Kina9N9Jw535IRCSwinD6a/2Ca
|
||||
m7DnVXFiVA==
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
|
||||
// LocalhostKey is the private key for localhostCert.
|
||||
var LocalhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA5vETjLa+8W856rWXO1xMF/CLss9vn5xZhPXKhgz+D7ogSAXm
|
||||
mWP53eeBUGC2r26J++CYfVqwOmfJEu9kkGUVi8cGMY9dHeIFPfxD31MYX175jJQe
|
||||
tu0WeUII7ciNsSUDyBMqsl7yi1IgN7iLONM++1+QfbbmNiEbghRV6icEH6M+bWlz
|
||||
3YSAMEdpK3mg2gsugfLKMwJkaBKEehUNMySRlIhyLITqt1exYGaggRd1zjqUpqpD
|
||||
sL2sRVHJ3qHGkSh8nVy8MvG8BXiFdYQJP3mCQDZzruCyMWj5/19KAyu7Cto3Bcvu
|
||||
PgujnwRtU+itt8WhZUVtU1n7Ivf6lMJTBcc4OQIDAQABAoIBAQCTLE0eHpPevtg0
|
||||
+FaRUMd5diVA5asoF3aBIjZXaU47bY0G+SO02x6wSMmDFK83a4Vpy/7B3Bp0jhF5
|
||||
DLCUyKaLdmE/EjLwSUq37ty+JHFizd7QtNBCGSN6URfpmSabHpCjX3uVQqblHIhF
|
||||
mki3BQCdJ5CoXPemxUCHjDgYSZb6JVNIPJExjekc0+4A2MYWMXV6Wr86C7AY3659
|
||||
KmveZpC3gOkLA/g/IqDQL/QgTq7/3eloHaO+uPBihdF56do4eaOO0jgFYpl8V7ek
|
||||
PZhHfhuPZV3oq15+8Vt77ngtjUWVI6qX0E3ilh+V5cof+03q0FzHPVe3zBUNXcm0
|
||||
OGz19u/FAoGBAPSm4Aa4xs/ybyjQakMNix9rak66ehzGkmlfeK5yuQ/fHmTg8Ac+
|
||||
ahGs6A3lFWQiyU6hqm6Qp0iKuxuDh35DJGCWAw5OUS/7WLJtu8fNFch6iIG29rFs
|
||||
s+Uz2YLxJPebpBsKymZUp7NyDRgEElkiqsREmbYjLrc8uNKkDy+k14YnAoGBAPGn
|
||||
ZlN0Mo5iNgQStulYEP5pI7WOOax9KOYVnBNguqgY9c7fXVXBxChoxt5ebQJWG45y
|
||||
KPG0hB0bkA4YPu4bTRf5acIMpjFwcxNlmwdc4oCkT4xqAFs9B/AKYZgkf4IfKHqW
|
||||
P9PD7TbUpkaxv25bPYwUSEB7lPa+hBtRyN9Wo6qfAoGAPBkeISiU1hJE0i7YW55h
|
||||
FZfKZoqSYq043B+ywo+1/Dsf+UH0VKM1ZSAnZPpoVc/hyaoW9tAb98r0iZ620wJl
|
||||
VkCjgYklknbY5APmw/8SIcxP6iVq1kzQqDYjcXIRVa3rEyWEcLzM8VzL8KFXbIQC
|
||||
lPIRHFfqKuMEt+HLRTXmJ7MCgYAHGvv4QjdmVl7uObqlG9DMGj1RjlAF0VxNf58q
|
||||
NrLmVG2N2qV86wigg4wtZ6te4TdINfUcPkmQLYpLz8yx5Z2bsdq5OPP+CidoD5nC
|
||||
WqnSTIKGR2uhQycjmLqL5a7WHaJsEFTqHh2wego1k+5kCUzC/KmvM7MKmkl6ICp+
|
||||
3qZLUwKBgQCDOhKDwYo1hdiXoOOQqg/LZmpWOqjO3b4p99B9iJqhmXN0GKXIPSBh
|
||||
5nqqmGsG8asSQhchs7EPMh8B80KbrDTeidWskZuUoQV27Al1UEmL6Zcl83qXD6sf
|
||||
k9X9TwWyZtp5IL1CAEd/Il9ZTXFzr3lNaN8LCFnU+EIsz1YgUW8LTg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`)
|
||||
|
||||
// NewLocalListener returns a new ipv4 "127.0.0.1:0"
|
||||
// or tcp6 "[::1]:0" tcp listener.
|
||||
func NewLocalListener() net.Listener {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// NewLocalTLSListener returns a new tls listener
|
||||
// based on the "tcpListener", if "tcpListener" is nil
|
||||
// it make use of the `NewLocalListener`.
|
||||
// Cert and Key are `LocalhostCert` and `LocalhostKey` respectfully.
|
||||
func NewLocalTLSListener(tcpListener net.Listener) net.Listener {
|
||||
if tcpListener == nil {
|
||||
tcpListener = NewLocalListener()
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cfg := new(tls.Config)
|
||||
cfg.NextProtos = []string{"http/1.1"}
|
||||
cfg.Certificates = []tls.Certificate{cert}
|
||||
cfg.InsecureSkipVerify = true
|
||||
return tls.NewListener(tcpListener, cfg)
|
||||
}
|
14
iris.go
14
iris.go
|
@ -6,6 +6,7 @@ package iris
|
|||
|
||||
import (
|
||||
// std packages
|
||||
stdContext "context"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
@ -79,6 +80,9 @@ type Application struct {
|
|||
sessions sessions.Sessions
|
||||
// used for build
|
||||
once sync.Once
|
||||
|
||||
mu sync.Mutex
|
||||
Shutdown func(stdContext.Context) error
|
||||
}
|
||||
|
||||
// New creates and returns a fresh empty Iris *Application instance.
|
||||
|
@ -149,6 +153,7 @@ func (app *Application) Build() (err error) {
|
|||
return // if view engine loading failed then don't continue
|
||||
}
|
||||
|
||||
if !app.Router.Downgraded() {
|
||||
var routerHandler router.RequestHandler
|
||||
// router
|
||||
// create the request handler, the default routing handler
|
||||
|
@ -157,6 +162,8 @@ func (app *Application) Build() (err error) {
|
|||
err = app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder)
|
||||
// re-build of the router from outside can be done with;
|
||||
// app.RefreshRouter()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
return
|
||||
|
@ -166,6 +173,9 @@ func (app *Application) Build() (err error) {
|
|||
// completes the necessary missing parts of that "srv"
|
||||
// and returns a new, ready-to-use, host (supervisor).
|
||||
func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
|
||||
app.mu.Lock()
|
||||
defer app.mu.Unlock()
|
||||
|
||||
// set the server's handler to the framework's router
|
||||
if srv.Handler == nil {
|
||||
srv.Handler = app.Router
|
||||
|
@ -216,6 +226,10 @@ func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
|
|||
su.Schedule(host.ShutdownOnInterruptTask(shutdownTimeout))
|
||||
}
|
||||
|
||||
if app.Shutdown == nil {
|
||||
app.Shutdown = su.Shutdown
|
||||
}
|
||||
|
||||
return su
|
||||
}
|
||||
|
||||
|
|
|
@ -102,20 +102,12 @@ func (app *Application) ListenTLS(addr string, certFile, keyFile string) {
|
|||
func (app *Application) ListenLETSENCRYPT(addr string, cacheDirOptional ...string) {
|
||||
l, err := nettools.LETSENCRYPT(addr, addr, cacheDirOptional...)
|
||||
CheckErr(err)
|
||||
|
||||
targetURL := nettools.SchemeHTTPS + "://" + nettools.ResolveVHost(addr)
|
||||
target, err := url.Parse(targetURL)
|
||||
// create the redirect server to redirect http://... to https://...
|
||||
hostname := nettools.ResolveHostname(addr)
|
||||
proxyAddr := hostname + ":80"
|
||||
target, err := url.Parse("https://" + hostname)
|
||||
CheckErr(err)
|
||||
// create the reverse proxy to redirect http://... to https://...
|
||||
proxyAddr := nettools.ResolveHostname(addr) + ":80"
|
||||
proxySrv := host.NewProxy(proxyAddr, target)
|
||||
|
||||
go func() {
|
||||
if err := proxySrv.ListenAndServe(); err != nil {
|
||||
// don't panic here, just log the proxy's error.
|
||||
app.Log("proxy error: %v", err)
|
||||
}
|
||||
}()
|
||||
go host.NewProxy(proxyAddr, target).ListenAndServe()
|
||||
|
||||
CheckErr(app.Run(Listener(l)))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user