1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-08 20:41:57 +00:00

a bit linting and some tests are online again

Former-commit-id: bd5463a169a36b078dba1c1b6e7dd3ffbd627617
This commit is contained in:
kataras
2017-06-10 03:31:50 +03:00
parent f747c682b9
commit c4f5fae561
21 changed files with 522 additions and 42 deletions

View File

@@ -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
View 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)
}

View File

@@ -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

View File

@@ -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 (

View File

@@ -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,

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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 (

View File

@@ -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)
}