Adding ability to serve TLS directly from Mattermost server (#4119)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5f8e5c401b
Коммит
8f91c77755
23
vendor/github.com/tylerb/graceful/.gitignore
сгенерированный
поставляемый
Обычный файл
23
vendor/github.com/tylerb/graceful/.gitignore
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
13
vendor/github.com/tylerb/graceful/.travis.yml
сгенерированный
поставляемый
Обычный файл
13
vendor/github.com/tylerb/graceful/.travis.yml
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,13 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.7
|
||||
- 1.6.2
|
||||
- 1.5.4
|
||||
- 1.4.3
|
||||
- 1.3.3
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||
21
vendor/github.com/tylerb/graceful/LICENSE
сгенерированный
поставляемый
Обычный файл
21
vendor/github.com/tylerb/graceful/LICENSE
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Tyler Bunnell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
152
vendor/github.com/tylerb/graceful/README.md
сгенерированный
поставляемый
Обычный файл
152
vendor/github.com/tylerb/graceful/README.md
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,152 @@
|
||||
graceful [](http://godoc.org/github.com/tylerb/graceful) [](https://travis-ci.org/tylerb/graceful) [](https://coveralls.io/r/tylerb/graceful) [](https://gitter.im/tylerb/graceful?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
========
|
||||
|
||||
Graceful is a Go 1.3+ package enabling graceful shutdown of http.Handler servers.
|
||||
|
||||
## Installation
|
||||
|
||||
To install, simply execute:
|
||||
|
||||
```
|
||||
go get gopkg.in/tylerb/graceful.v1
|
||||
```
|
||||
|
||||
I am using [gopkg.in](http://labix.org/gopkg.in) to control releases.
|
||||
|
||||
## Usage
|
||||
|
||||
Using Graceful is easy. Simply create your http.Handler and pass it to the `Run` function:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/tylerb/graceful.v1"
|
||||
"net/http"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, "Welcome to the home page!")
|
||||
})
|
||||
|
||||
graceful.Run(":3001",10*time.Second,mux)
|
||||
}
|
||||
```
|
||||
|
||||
Another example, using [Negroni](https://github.com/codegangsta/negroni), functions in much the same manner:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/negroni"
|
||||
"gopkg.in/tylerb/graceful.v1"
|
||||
"net/http"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, "Welcome to the home page!")
|
||||
})
|
||||
|
||||
n := negroni.Classic()
|
||||
n.UseHandler(mux)
|
||||
//n.Run(":3000")
|
||||
graceful.Run(":3001",10*time.Second,n)
|
||||
}
|
||||
```
|
||||
|
||||
In addition to Run there are the http.Server counterparts ListenAndServe, ListenAndServeTLS and Serve, which allow you to configure HTTPS, custom timeouts and error handling.
|
||||
Graceful may also be used by instantiating its Server type directly, which embeds an http.Server:
|
||||
|
||||
```go
|
||||
mux := // ...
|
||||
|
||||
srv := &graceful.Server{
|
||||
Timeout: 10 * time.Second,
|
||||
|
||||
Server: &http.Server{
|
||||
Addr: ":1234",
|
||||
Handler: mux,
|
||||
},
|
||||
}
|
||||
|
||||
srv.ListenAndServe()
|
||||
```
|
||||
|
||||
This form allows you to set the ConnState callback, which works in the same way as in http.Server:
|
||||
|
||||
```go
|
||||
mux := // ...
|
||||
|
||||
srv := &graceful.Server{
|
||||
Timeout: 10 * time.Second,
|
||||
|
||||
ConnState: func(conn net.Conn, state http.ConnState) {
|
||||
// conn has a new state
|
||||
},
|
||||
|
||||
Server: &http.Server{
|
||||
Addr: ":1234",
|
||||
Handler: mux,
|
||||
},
|
||||
}
|
||||
|
||||
srv.ListenAndServe()
|
||||
```
|
||||
|
||||
## Behaviour
|
||||
|
||||
When Graceful is sent a SIGINT or SIGTERM (possibly from ^C or a kill command), it:
|
||||
|
||||
1. Disables keepalive connections.
|
||||
2. Closes the listening socket, allowing another process to listen on that port immediately.
|
||||
3. Starts a timer of `timeout` duration to give active requests a chance to finish.
|
||||
4. When timeout expires, closes all active connections.
|
||||
5. Closes the `stopChan`, waking up any blocking goroutines.
|
||||
6. Returns from the function, allowing the server to terminate.
|
||||
|
||||
## Notes
|
||||
|
||||
If the `timeout` argument to `Run` is 0, the server never times out, allowing all active requests to complete.
|
||||
|
||||
If you wish to stop the server in some way other than an OS signal, you may call the `Stop()` function.
|
||||
This function stops the server, gracefully, using the new timeout value you provide. The `StopChan()` function
|
||||
returns a channel on which you can block while waiting for the server to stop. This channel will be closed when
|
||||
the server is stopped, allowing your execution to proceed. Multiple goroutines can block on this channel at the
|
||||
same time and all will be signalled when stopping is complete.
|
||||
|
||||
### Important things to note when setting `timeout` to 0:
|
||||
|
||||
If you set the `timeout` to `0`, it waits for all connections to the server to disconnect before shutting down.
|
||||
This means that even though requests over a connection have finished, it is possible for the client to hold the
|
||||
connection open and block the server from shutting down indefinitely.
|
||||
|
||||
This is especially evident when graceful is used to run HTTP/2 servers. Clients like Chrome and Firefox have been
|
||||
observed to hold onto the open connection indefinitely over HTTP/2, preventing the server from shutting down. In
|
||||
addition, there is also the risk of malicious clients holding and keeping the connection alive.
|
||||
|
||||
It is understandable that sometimes, you might want to wait for the client indefinitely because they might be
|
||||
uploading large files. In these type of cases, it is recommended that you set a reasonable timeout to kill the
|
||||
connection, and have the client perform resumable uploads. For example, the client can divide the file into chunks
|
||||
and reupload chunks that were in transit when the connection was terminated.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you would like to contribute, please:
|
||||
|
||||
1. Create a GitHub issue regarding the contribution. Features and bugs should be discussed beforehand.
|
||||
2. Fork the repository.
|
||||
3. Create a pull request with your solution. This pull request should reference and close the issues (Fix #2).
|
||||
|
||||
All pull requests should:
|
||||
|
||||
1. Pass [gometalinter -t .](https://github.com/alecthomas/gometalinter) with no warnings.
|
||||
2. Be `go fmt` formatted.
|
||||
487
vendor/github.com/tylerb/graceful/graceful.go
сгенерированный
поставляемый
Обычный файл
487
vendor/github.com/tylerb/graceful/graceful.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,487 @@
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Server wraps an http.Server with graceful connection handling.
|
||||
// It may be used directly in the same way as http.Server, or may
|
||||
// be constructed with the global functions in this package.
|
||||
//
|
||||
// Example:
|
||||
// srv := &graceful.Server{
|
||||
// Timeout: 5 * time.Second,
|
||||
// Server: &http.Server{Addr: ":1234", Handler: handler},
|
||||
// }
|
||||
// srv.ListenAndServe()
|
||||
type Server struct {
|
||||
*http.Server
|
||||
|
||||
// Timeout is the duration to allow outstanding requests to survive
|
||||
// before forcefully terminating them.
|
||||
Timeout time.Duration
|
||||
|
||||
// Limit the number of outstanding requests
|
||||
ListenLimit int
|
||||
|
||||
// TCPKeepAlive sets the TCP keep-alive timeouts on accepted
|
||||
// connections. It prunes dead TCP connections ( e.g. closing
|
||||
// laptop mid-download)
|
||||
TCPKeepAlive time.Duration
|
||||
|
||||
// ConnState specifies an optional callback function that is
|
||||
// called when a client connection changes state. This is a proxy
|
||||
// to the underlying http.Server's ConnState, and the original
|
||||
// must not be set directly.
|
||||
ConnState func(net.Conn, http.ConnState)
|
||||
|
||||
// BeforeShutdown is an optional callback function that is called
|
||||
// before the listener is closed. Returns true if shutdown is allowed
|
||||
BeforeShutdown func() bool
|
||||
|
||||
// ShutdownInitiated is an optional callback function that is called
|
||||
// when shutdown is initiated. It can be used to notify the client
|
||||
// side of long lived connections (e.g. websockets) to reconnect.
|
||||
ShutdownInitiated func()
|
||||
|
||||
// NoSignalHandling prevents graceful from automatically shutting down
|
||||
// on SIGINT and SIGTERM. If set to true, you must shut down the server
|
||||
// manually with Stop().
|
||||
NoSignalHandling bool
|
||||
|
||||
// Logger used to notify of errors on startup and on stop.
|
||||
Logger *log.Logger
|
||||
|
||||
// LogFunc can be assigned with a logging function of your choice, allowing
|
||||
// you to use whatever logging approach you would like
|
||||
LogFunc func(format string, args ...interface{})
|
||||
|
||||
// Interrupted is true if the server is handling a SIGINT or SIGTERM
|
||||
// signal and is thus shutting down.
|
||||
Interrupted bool
|
||||
|
||||
// interrupt signals the listener to stop serving connections,
|
||||
// and the server to shut down.
|
||||
interrupt chan os.Signal
|
||||
|
||||
// stopLock is used to protect against concurrent calls to Stop
|
||||
stopLock sync.Mutex
|
||||
|
||||
// stopChan is the channel on which callers may block while waiting for
|
||||
// the server to stop.
|
||||
stopChan chan struct{}
|
||||
|
||||
// chanLock is used to protect access to the various channel constructors.
|
||||
chanLock sync.RWMutex
|
||||
|
||||
// connections holds all connections managed by graceful
|
||||
connections map[net.Conn]struct{}
|
||||
|
||||
// idleConnections holds all idle connections managed by graceful
|
||||
idleConnections map[net.Conn]struct{}
|
||||
}
|
||||
|
||||
// Run serves the http.Handler with graceful shutdown enabled.
|
||||
//
|
||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
||||
func Run(addr string, timeout time.Duration, n http.Handler) {
|
||||
srv := &Server{
|
||||
Timeout: timeout,
|
||||
TCPKeepAlive: 3 * time.Minute,
|
||||
Server: &http.Server{Addr: addr, Handler: n},
|
||||
// Logger: DefaultLogger(),
|
||||
}
|
||||
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
if opErr, ok := err.(*net.OpError); !ok || (ok && opErr.Op != "accept") {
|
||||
srv.logf("%s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// RunWithErr is an alternative version of Run function which can return error.
|
||||
//
|
||||
// Unlike Run this version will not exit the program if an error is encountered but will
|
||||
// return it instead.
|
||||
func RunWithErr(addr string, timeout time.Duration, n http.Handler) error {
|
||||
srv := &Server{
|
||||
Timeout: timeout,
|
||||
TCPKeepAlive: 3 * time.Minute,
|
||||
Server: &http.Server{Addr: addr, Handler: n},
|
||||
Logger: DefaultLogger(),
|
||||
}
|
||||
|
||||
return srv.ListenAndServe()
|
||||
}
|
||||
|
||||
// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
|
||||
//
|
||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
||||
func ListenAndServe(server *http.Server, timeout time.Duration) error {
|
||||
srv := &Server{Timeout: timeout, Server: server, Logger: DefaultLogger()}
|
||||
return srv.ListenAndServe()
|
||||
}
|
||||
|
||||
// ListenAndServe is equivalent to http.Server.ListenAndServe with graceful shutdown enabled.
|
||||
func (srv *Server) ListenAndServe() error {
|
||||
// Create the listener so we can control their lifetime
|
||||
addr := srv.Addr
|
||||
if addr == "" {
|
||||
addr = ":http"
|
||||
}
|
||||
conn, err := srv.newTCPListener(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return srv.Serve(conn)
|
||||
}
|
||||
|
||||
// ListenAndServeTLS is equivalent to http.Server.ListenAndServeTLS with graceful shutdown enabled.
|
||||
//
|
||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
||||
func ListenAndServeTLS(server *http.Server, certFile, keyFile string, timeout time.Duration) error {
|
||||
srv := &Server{Timeout: timeout, Server: server, Logger: DefaultLogger()}
|
||||
return srv.ListenAndServeTLS(certFile, keyFile)
|
||||
}
|
||||
|
||||
// ListenTLS is a convenience method that creates an https listener using the
|
||||
// provided cert and key files. Use this method if you need access to the
|
||||
// listener object directly. When ready, pass it to the Serve method.
|
||||
func (srv *Server) ListenTLS(certFile, keyFile string) (net.Listener, error) {
|
||||
// Create the listener ourselves so we can control its lifetime
|
||||
addr := srv.Addr
|
||||
if addr == "" {
|
||||
addr = ":https"
|
||||
}
|
||||
|
||||
config := &tls.Config{}
|
||||
if srv.TLSConfig != nil {
|
||||
*config = *srv.TLSConfig
|
||||
}
|
||||
|
||||
var err error
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enable http2
|
||||
enableHTTP2ForTLSConfig(config)
|
||||
|
||||
conn, err := srv.newTCPListener(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.TLSConfig = config
|
||||
|
||||
tlsListener := tls.NewListener(conn, config)
|
||||
return tlsListener, nil
|
||||
}
|
||||
|
||||
// Enable HTTP2ForTLSConfig explicitly enables http/2 for a TLS Config. This is due to changes in Go 1.7 where
|
||||
// http servers are no longer automatically configured to enable http/2 if the server's TLSConfig is set.
|
||||
// See https://github.com/golang/go/issues/15908
|
||||
func enableHTTP2ForTLSConfig(t *tls.Config) {
|
||||
|
||||
if TLSConfigHasHTTP2Enabled(t) {
|
||||
return
|
||||
}
|
||||
|
||||
t.NextProtos = append(t.NextProtos, "h2")
|
||||
}
|
||||
|
||||
// TLSConfigHasHTTP2Enabled checks to see if a given TLS Config has http2 enabled.
|
||||
func TLSConfigHasHTTP2Enabled(t *tls.Config) bool {
|
||||
for _, value := range t.NextProtos {
|
||||
if value == "h2" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ListenAndServeTLS is equivalent to http.Server.ListenAndServeTLS with graceful shutdown enabled.
|
||||
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error {
|
||||
l, err := srv.ListenTLS(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return srv.Serve(l)
|
||||
}
|
||||
|
||||
// ListenAndServeTLSConfig can be used with an existing TLS config and is equivalent to
|
||||
// http.Server.ListenAndServeTLS with graceful shutdown enabled,
|
||||
func (srv *Server) ListenAndServeTLSConfig(config *tls.Config) error {
|
||||
addr := srv.Addr
|
||||
if addr == "" {
|
||||
addr = ":https"
|
||||
}
|
||||
|
||||
conn, err := srv.newTCPListener(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srv.TLSConfig = config
|
||||
|
||||
tlsListener := tls.NewListener(conn, config)
|
||||
return srv.Serve(tlsListener)
|
||||
}
|
||||
|
||||
// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
|
||||
//
|
||||
// timeout is the duration to wait until killing active requests and stopping the server.
|
||||
// If timeout is 0, the server never times out. It waits for all active requests to finish.
|
||||
func Serve(server *http.Server, l net.Listener, timeout time.Duration) error {
|
||||
srv := &Server{Timeout: timeout, Server: server, Logger: DefaultLogger()}
|
||||
|
||||
return srv.Serve(l)
|
||||
}
|
||||
|
||||
// Serve is equivalent to http.Server.Serve with graceful shutdown enabled.
|
||||
func (srv *Server) Serve(listener net.Listener) error {
|
||||
|
||||
if srv.ListenLimit != 0 {
|
||||
listener = LimitListener(listener, srv.ListenLimit)
|
||||
}
|
||||
|
||||
// Make our stopchan
|
||||
srv.StopChan()
|
||||
|
||||
// Track connection state
|
||||
add := make(chan net.Conn)
|
||||
idle := make(chan net.Conn)
|
||||
active := make(chan net.Conn)
|
||||
remove := make(chan net.Conn)
|
||||
|
||||
srv.Server.ConnState = func(conn net.Conn, state http.ConnState) {
|
||||
switch state {
|
||||
case http.StateNew:
|
||||
add <- conn
|
||||
case http.StateActive:
|
||||
active <- conn
|
||||
case http.StateIdle:
|
||||
idle <- conn
|
||||
case http.StateClosed, http.StateHijacked:
|
||||
remove <- conn
|
||||
}
|
||||
|
||||
srv.stopLock.Lock()
|
||||
defer srv.stopLock.Unlock()
|
||||
|
||||
if srv.ConnState != nil {
|
||||
srv.ConnState(conn, state)
|
||||
}
|
||||
}
|
||||
|
||||
// Manage open connections
|
||||
shutdown := make(chan chan struct{})
|
||||
kill := make(chan struct{})
|
||||
go srv.manageConnections(add, idle, active, remove, shutdown, kill)
|
||||
|
||||
interrupt := srv.interruptChan()
|
||||
// Set up the interrupt handler
|
||||
if !srv.NoSignalHandling {
|
||||
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
}
|
||||
quitting := make(chan struct{})
|
||||
go srv.handleInterrupt(interrupt, quitting, listener)
|
||||
|
||||
// Serve with graceful listener.
|
||||
// Execution blocks here until listener.Close() is called, above.
|
||||
err := srv.Server.Serve(listener)
|
||||
if err != nil {
|
||||
// If the underlying listening is closed, Serve returns an error
|
||||
// complaining about listening on a closed socket. This is expected, so
|
||||
// let's ignore the error if we are the ones who explicitly closed the
|
||||
// socket.
|
||||
select {
|
||||
case <-quitting:
|
||||
err = nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
srv.shutdown(shutdown, kill)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop instructs the type to halt operations and close
|
||||
// the stop channel when it is finished.
|
||||
//
|
||||
// timeout is grace period for which to wait before shutting
|
||||
// down the server. The timeout value passed here will override the
|
||||
// timeout given when constructing the server, as this is an explicit
|
||||
// command to stop the server.
|
||||
func (srv *Server) Stop(timeout time.Duration) {
|
||||
srv.stopLock.Lock()
|
||||
defer srv.stopLock.Unlock()
|
||||
|
||||
srv.Timeout = timeout
|
||||
interrupt := srv.interruptChan()
|
||||
interrupt <- syscall.SIGINT
|
||||
}
|
||||
|
||||
// StopChan gets the stop channel which will block until
|
||||
// stopping has completed, at which point it is closed.
|
||||
// Callers should never close the stop channel.
|
||||
func (srv *Server) StopChan() <-chan struct{} {
|
||||
srv.chanLock.Lock()
|
||||
defer srv.chanLock.Unlock()
|
||||
|
||||
if srv.stopChan == nil {
|
||||
srv.stopChan = make(chan struct{})
|
||||
}
|
||||
return srv.stopChan
|
||||
}
|
||||
|
||||
// DefaultLogger returns the logger used by Run, RunWithErr, ListenAndServe, ListenAndServeTLS and Serve.
|
||||
// The logger outputs to STDERR by default.
|
||||
func DefaultLogger() *log.Logger {
|
||||
return log.New(os.Stderr, "[graceful] ", 0)
|
||||
}
|
||||
|
||||
func (srv *Server) manageConnections(add, idle, active, remove chan net.Conn, shutdown chan chan struct{}, kill chan struct{}) {
|
||||
var done chan struct{}
|
||||
srv.connections = map[net.Conn]struct{}{}
|
||||
srv.idleConnections = map[net.Conn]struct{}{}
|
||||
for {
|
||||
select {
|
||||
case conn := <-add:
|
||||
srv.connections[conn] = struct{}{}
|
||||
case conn := <-idle:
|
||||
srv.idleConnections[conn] = struct{}{}
|
||||
case conn := <-active:
|
||||
delete(srv.idleConnections, conn)
|
||||
case conn := <-remove:
|
||||
delete(srv.connections, conn)
|
||||
delete(srv.idleConnections, conn)
|
||||
if done != nil && len(srv.connections) == 0 {
|
||||
done <- struct{}{}
|
||||
return
|
||||
}
|
||||
case done = <-shutdown:
|
||||
if len(srv.connections) == 0 && len(srv.idleConnections) == 0 {
|
||||
done <- struct{}{}
|
||||
return
|
||||
}
|
||||
// a shutdown request has been received. if we have open idle
|
||||
// connections, we must close all of them now. this prevents idle
|
||||
// connections from holding the server open while waiting for them to
|
||||
// hit their idle timeout.
|
||||
for k := range srv.idleConnections {
|
||||
if err := k.Close(); err != nil {
|
||||
srv.logf("[ERROR] %s", err)
|
||||
}
|
||||
}
|
||||
case <-kill:
|
||||
srv.stopLock.Lock()
|
||||
defer srv.stopLock.Unlock()
|
||||
|
||||
srv.Server.ConnState = nil
|
||||
for k := range srv.connections {
|
||||
if err := k.Close(); err != nil {
|
||||
srv.logf("[ERROR] %s", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) interruptChan() chan os.Signal {
|
||||
srv.chanLock.Lock()
|
||||
defer srv.chanLock.Unlock()
|
||||
|
||||
if srv.interrupt == nil {
|
||||
srv.interrupt = make(chan os.Signal, 1)
|
||||
}
|
||||
|
||||
return srv.interrupt
|
||||
}
|
||||
|
||||
func (srv *Server) handleInterrupt(interrupt chan os.Signal, quitting chan struct{}, listener net.Listener) {
|
||||
for _ = range interrupt {
|
||||
if srv.Interrupted {
|
||||
srv.logf("already shutting down")
|
||||
continue
|
||||
}
|
||||
srv.logf("shutdown initiated")
|
||||
srv.Interrupted = true
|
||||
if srv.BeforeShutdown != nil {
|
||||
if !srv.BeforeShutdown() {
|
||||
srv.Interrupted = false
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
close(quitting)
|
||||
srv.SetKeepAlivesEnabled(false)
|
||||
if err := listener.Close(); err != nil {
|
||||
srv.logf("[ERROR] %s", err)
|
||||
}
|
||||
|
||||
if srv.ShutdownInitiated != nil {
|
||||
srv.ShutdownInitiated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) logf(format string, args ...interface{}) {
|
||||
if srv.LogFunc != nil {
|
||||
srv.LogFunc(format, args...)
|
||||
} else if srv.Logger != nil {
|
||||
srv.Logger.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *Server) shutdown(shutdown chan chan struct{}, kill chan struct{}) {
|
||||
// Request done notification
|
||||
done := make(chan struct{})
|
||||
shutdown <- done
|
||||
|
||||
if srv.Timeout > 0 {
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(srv.Timeout):
|
||||
close(kill)
|
||||
}
|
||||
} else {
|
||||
<-done
|
||||
}
|
||||
// Close the stopChan to wake up any blocked goroutines.
|
||||
srv.chanLock.Lock()
|
||||
if srv.stopChan != nil {
|
||||
close(srv.stopChan)
|
||||
}
|
||||
srv.chanLock.Unlock()
|
||||
}
|
||||
|
||||
func (srv *Server) newTCPListener(addr string) (net.Listener, error) {
|
||||
conn, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
if srv.TCPKeepAlive != 0 {
|
||||
conn = keepAliveListener{conn, srv.TCPKeepAlive}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
692
vendor/github.com/tylerb/graceful/graceful_test.go
сгенерированный
поставляемый
Обычный файл
692
vendor/github.com/tylerb/graceful/graceful_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,692 @@
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// The tests will run a test server on this port.
|
||||
port = 9654
|
||||
concurrentRequestN = 8
|
||||
killTime = 500 * time.Millisecond
|
||||
timeoutTime = 1000 * time.Millisecond
|
||||
waitTime = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
func runQuery(t *testing.T, expected int, shouldErr bool, wg *sync.WaitGroup, once *sync.Once) {
|
||||
defer wg.Done()
|
||||
client := http.Client{}
|
||||
r, err := client.Get(fmt.Sprintf("http://localhost:%d", port))
|
||||
if shouldErr && err == nil {
|
||||
once.Do(func() {
|
||||
t.Error("Expected an error but none was encountered.")
|
||||
})
|
||||
} else if shouldErr && err != nil {
|
||||
if checkErr(t, err, once) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if r != nil && r.StatusCode != expected {
|
||||
once.Do(func() {
|
||||
t.Errorf("Incorrect status code on response. Expected %d. Got %d", expected, r.StatusCode)
|
||||
})
|
||||
} else if r == nil {
|
||||
once.Do(func() {
|
||||
t.Error("No response when a response was expected.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkErr(t *testing.T, err error, once *sync.Once) bool {
|
||||
if err.(*url.Error).Err == io.EOF {
|
||||
return true
|
||||
}
|
||||
var errno syscall.Errno
|
||||
switch oe := err.(*url.Error).Err.(type) {
|
||||
case *net.OpError:
|
||||
switch e := oe.Err.(type) {
|
||||
case syscall.Errno:
|
||||
errno = e
|
||||
case *os.SyscallError:
|
||||
errno = e.Err.(syscall.Errno)
|
||||
}
|
||||
if errno == syscall.ECONNREFUSED {
|
||||
return true
|
||||
} else if err != nil {
|
||||
once.Do(func() {
|
||||
t.Error("Error on Get:", err)
|
||||
})
|
||||
}
|
||||
default:
|
||||
if strings.Contains(err.Error(), "transport closed before response was received") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(err.Error(), "server closed connection") {
|
||||
return true
|
||||
}
|
||||
fmt.Printf("unknown err: %s, %#v\n", err, err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func createListener(sleep time.Duration) (*http.Server, net.Listener, error) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(sleep)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: mux}
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
return server, l, err
|
||||
}
|
||||
|
||||
func launchTestQueries(t *testing.T, wg *sync.WaitGroup, c chan os.Signal) {
|
||||
defer wg.Done()
|
||||
var once sync.Once
|
||||
|
||||
for i := 0; i < concurrentRequestN; i++ {
|
||||
wg.Add(1)
|
||||
go runQuery(t, http.StatusOK, false, wg, &once)
|
||||
}
|
||||
|
||||
time.Sleep(waitTime)
|
||||
c <- os.Interrupt
|
||||
time.Sleep(waitTime)
|
||||
|
||||
for i := 0; i < concurrentRequestN; i++ {
|
||||
wg.Add(1)
|
||||
go runQuery(t, 0, true, wg, &once)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGracefulRun(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
server, l, err := createListener(killTime / 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv := &Server{Timeout: killTime, Server: server, interrupt: c}
|
||||
srv.Serve(l)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go launchTestQueries(t, &wg, c)
|
||||
}
|
||||
|
||||
func TestGracefulRunLimitKeepAliveListener(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
server, l, err := createListener(killTime / 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv := &Server{
|
||||
Timeout: killTime,
|
||||
ListenLimit: concurrentRequestN,
|
||||
TCPKeepAlive: 1 * time.Second,
|
||||
Server: server,
|
||||
interrupt: c,
|
||||
}
|
||||
srv.Serve(l)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go launchTestQueries(t, &wg, c)
|
||||
}
|
||||
|
||||
func TestGracefulRunTimesOut(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
server, l, err := createListener(killTime * 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv := &Server{Timeout: killTime, Server: server, interrupt: c}
|
||||
srv.Serve(l)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var once sync.Once
|
||||
|
||||
for i := 0; i < concurrentRequestN; i++ {
|
||||
wg.Add(1)
|
||||
go runQuery(t, 0, true, &wg, &once)
|
||||
}
|
||||
|
||||
time.Sleep(waitTime)
|
||||
c <- os.Interrupt
|
||||
time.Sleep(waitTime)
|
||||
|
||||
for i := 0; i < concurrentRequestN; i++ {
|
||||
wg.Add(1)
|
||||
go runQuery(t, 0, true, &wg, &once)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TestGracefulRunDoesntTimeOut(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
server, l, err := createListener(killTime * 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv := &Server{Timeout: 0, Server: server, interrupt: c}
|
||||
srv.Serve(l)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go launchTestQueries(t, &wg, c)
|
||||
}
|
||||
|
||||
func TestGracefulRunDoesntTimeOutAfterConnectionCreated(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
server, l, err := createListener(killTime)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv := &Server{Timeout: 0, Server: server, interrupt: c}
|
||||
srv.Serve(l)
|
||||
}()
|
||||
time.Sleep(waitTime)
|
||||
|
||||
// Make a sample first request. The connection will be left idle.
|
||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d", port))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("first request failed: %v", err))
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
// With idle connections improperly handled, the server doesn't wait for this
|
||||
// to complete and the request fails. It should be allowed to complete successfully.
|
||||
_, err := http.Get(fmt.Sprintf("http://localhost:%d", port))
|
||||
if err != nil {
|
||||
t.Errorf("Get failed: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Ensure the request goes out
|
||||
time.Sleep(waitTime)
|
||||
c <- os.Interrupt
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestGracefulRunNoRequests(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
server, l, err := createListener(killTime * 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv := &Server{Timeout: 0, Server: server, interrupt: c}
|
||||
srv.Serve(l)
|
||||
}()
|
||||
|
||||
c <- os.Interrupt
|
||||
}
|
||||
|
||||
func TestGracefulForwardsConnState(t *testing.T) {
|
||||
var stateLock sync.Mutex
|
||||
states := make(map[http.ConnState]int)
|
||||
connState := func(conn net.Conn, state http.ConnState) {
|
||||
stateLock.Lock()
|
||||
states[state]++
|
||||
stateLock.Unlock()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
expected := map[http.ConnState]int{
|
||||
http.StateNew: concurrentRequestN,
|
||||
http.StateActive: concurrentRequestN,
|
||||
http.StateClosed: concurrentRequestN,
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
server, l, err := createListener(killTime / 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv := &Server{
|
||||
ConnState: connState,
|
||||
Timeout: killTime,
|
||||
Server: server,
|
||||
interrupt: c,
|
||||
}
|
||||
srv.Serve(l)
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go launchTestQueries(t, &wg, c)
|
||||
wg.Wait()
|
||||
|
||||
stateLock.Lock()
|
||||
if !reflect.DeepEqual(states, expected) {
|
||||
t.Errorf("Incorrect connection state tracking.\n actual: %v\nexpected: %v\n", states, expected)
|
||||
}
|
||||
stateLock.Unlock()
|
||||
}
|
||||
|
||||
func TestGracefulExplicitStop(t *testing.T) {
|
||||
server, l, err := createListener(1 * time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := &Server{Timeout: killTime, Server: server}
|
||||
|
||||
go func() {
|
||||
go srv.Serve(l)
|
||||
time.Sleep(waitTime)
|
||||
srv.Stop(killTime)
|
||||
}()
|
||||
|
||||
// block on the stopChan until the server has shut down
|
||||
select {
|
||||
case <-srv.StopChan():
|
||||
case <-time.After(timeoutTime):
|
||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGracefulExplicitStopOverride(t *testing.T) {
|
||||
server, l, err := createListener(1 * time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := &Server{Timeout: killTime, Server: server}
|
||||
|
||||
go func() {
|
||||
go srv.Serve(l)
|
||||
time.Sleep(waitTime)
|
||||
srv.Stop(killTime / 2)
|
||||
}()
|
||||
|
||||
// block on the stopChan until the server has shut down
|
||||
select {
|
||||
case <-srv.StopChan():
|
||||
case <-time.After(killTime):
|
||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBeforeShutdownAndShutdownInitiatedCallbacks(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
server, l, err := createListener(1 * time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
beforeShutdownCalled := make(chan struct{})
|
||||
cb1 := func() bool { close(beforeShutdownCalled); return true }
|
||||
shutdownInitiatedCalled := make(chan struct{})
|
||||
cb2 := func() { close(shutdownInitiatedCalled) }
|
||||
|
||||
wg.Add(2)
|
||||
srv := &Server{Server: server, BeforeShutdown: cb1, ShutdownInitiated: cb2}
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv.Serve(l)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(waitTime)
|
||||
srv.Stop(killTime)
|
||||
}()
|
||||
|
||||
beforeShutdown := false
|
||||
shutdownInitiated := false
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case <-beforeShutdownCalled:
|
||||
beforeShutdownCalled = nil
|
||||
beforeShutdown = true
|
||||
case <-shutdownInitiatedCalled:
|
||||
shutdownInitiatedCalled = nil
|
||||
shutdownInitiated = true
|
||||
case <-time.After(killTime):
|
||||
t.Fatal("Timed out while waiting for ShutdownInitiated callback to be called")
|
||||
}
|
||||
}
|
||||
|
||||
if !beforeShutdown {
|
||||
t.Fatal("beforeShutdown should be true")
|
||||
}
|
||||
if !shutdownInitiated {
|
||||
t.Fatal("shutdownInitiated should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBeforeShutdownCanceled(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
server, l, err := createListener(1 * time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
beforeShutdownCalled := make(chan struct{})
|
||||
cb1 := func() bool { close(beforeShutdownCalled); return false }
|
||||
shutdownInitiatedCalled := make(chan struct{})
|
||||
cb2 := func() { close(shutdownInitiatedCalled) }
|
||||
|
||||
srv := &Server{Server: server, BeforeShutdown: cb1, ShutdownInitiated: cb2}
|
||||
go func() {
|
||||
srv.Serve(l)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
time.Sleep(waitTime)
|
||||
srv.Stop(killTime)
|
||||
}()
|
||||
|
||||
beforeShutdown := false
|
||||
shutdownInitiated := false
|
||||
timeouted := false
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case <-beforeShutdownCalled:
|
||||
beforeShutdownCalled = nil
|
||||
beforeShutdown = true
|
||||
case <-shutdownInitiatedCalled:
|
||||
shutdownInitiatedCalled = nil
|
||||
shutdownInitiated = true
|
||||
case <-time.After(killTime):
|
||||
timeouted = true
|
||||
}
|
||||
}
|
||||
|
||||
if !beforeShutdown {
|
||||
t.Fatal("beforeShutdown should be true")
|
||||
}
|
||||
if !timeouted {
|
||||
t.Fatal("timeouted should be true")
|
||||
}
|
||||
if shutdownInitiated {
|
||||
t.Fatal("shutdownInitiated shouldn't be true")
|
||||
}
|
||||
|
||||
srv.BeforeShutdown = func() bool { return true }
|
||||
srv.Stop(killTime)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func hijackingListener(srv *Server) (*http.Server, net.Listener, error) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
conn, bufrw, err := rw.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(rw, "webserver doesn't support hijacking", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
bufrw.WriteString("HTTP/1.1 200 OK\r\n\r\n")
|
||||
bufrw.Flush()
|
||||
})
|
||||
|
||||
server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: mux}
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
return server, l, err
|
||||
}
|
||||
|
||||
func TestNotifyClosed(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
srv := &Server{Timeout: killTime, interrupt: c}
|
||||
server, l, err := hijackingListener(srv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv.Server = server
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv.Serve(l)
|
||||
}()
|
||||
|
||||
var once sync.Once
|
||||
for i := 0; i < concurrentRequestN; i++ {
|
||||
wg.Add(1)
|
||||
runQuery(t, http.StatusOK, false, &wg, &once)
|
||||
}
|
||||
|
||||
srv.Stop(0)
|
||||
|
||||
// block on the stopChan until the server has shut down
|
||||
select {
|
||||
case <-srv.StopChan():
|
||||
case <-time.After(timeoutTime):
|
||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||
}
|
||||
|
||||
if len(srv.connections) > 0 {
|
||||
t.Fatal("hijacked connections should not be managed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStopDeadlock(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
c := make(chan struct{})
|
||||
server, l, err := createListener(1 * time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := &Server{Server: server, NoSignalHandling: true}
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(waitTime)
|
||||
srv.Serve(l)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
srv.Stop(0)
|
||||
close(c)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-c:
|
||||
l.Close()
|
||||
case <-time.After(timeoutTime):
|
||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||
}
|
||||
}
|
||||
|
||||
// Run with --race
|
||||
func TestStopRace(t *testing.T) {
|
||||
server, l, err := createListener(1 * time.Millisecond)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
srv := &Server{Timeout: killTime, Server: server}
|
||||
|
||||
go func() {
|
||||
go srv.Serve(l)
|
||||
srv.Stop(killTime)
|
||||
}()
|
||||
srv.Stop(0)
|
||||
select {
|
||||
case <-srv.StopChan():
|
||||
case <-time.After(timeoutTime):
|
||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterruptLog(t *testing.T) {
|
||||
c := make(chan os.Signal, 1)
|
||||
|
||||
server, l, err := createListener(killTime * 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var tbuf bytes.Buffer
|
||||
logger := log.New(&buf, "", 0)
|
||||
expected := log.New(&tbuf, "", 0)
|
||||
|
||||
srv := &Server{Timeout: killTime, Server: server, Logger: logger, interrupt: c}
|
||||
go func() { srv.Serve(l) }()
|
||||
|
||||
stop := srv.StopChan()
|
||||
c <- os.Interrupt
|
||||
expected.Print("shutdown initiated")
|
||||
|
||||
<-stop
|
||||
|
||||
if buf.String() != tbuf.String() {
|
||||
t.Fatal("shutdown log incorrect - got '" + buf.String() + "'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiInterrupts(t *testing.T) {
|
||||
c := make(chan os.Signal, 1)
|
||||
|
||||
server, l, err := createListener(killTime * 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var bu bytes.Buffer
|
||||
buf := SyncBuffer{&wg, &bu}
|
||||
var tbuf bytes.Buffer
|
||||
logger := log.New(&buf, "", 0)
|
||||
expected := log.New(&tbuf, "", 0)
|
||||
|
||||
srv := &Server{Timeout: killTime, Server: server, Logger: logger, interrupt: c}
|
||||
go func() { srv.Serve(l) }()
|
||||
|
||||
stop := srv.StopChan()
|
||||
buf.Add(1 + 10) // Expecting 11 log calls
|
||||
c <- os.Interrupt
|
||||
expected.Printf("shutdown initiated")
|
||||
for i := 0; i < 10; i++ {
|
||||
c <- os.Interrupt
|
||||
expected.Printf("already shutting down")
|
||||
}
|
||||
|
||||
<-stop
|
||||
|
||||
wg.Wait()
|
||||
bb, bt := buf.Bytes(), tbuf.Bytes()
|
||||
for i, b := range bb {
|
||||
if b != bt[i] {
|
||||
t.Fatal(fmt.Sprintf("shutdown log incorrect - got '%s', expected '%s'", buf.String(), tbuf.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogFunc(t *testing.T) {
|
||||
c := make(chan os.Signal, 1)
|
||||
|
||||
server, l, err := createListener(killTime * 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var called bool
|
||||
srv := &Server{Timeout: killTime, Server: server,
|
||||
LogFunc: func(format string, args ...interface{}) {
|
||||
called = true
|
||||
}, interrupt: c}
|
||||
stop := srv.StopChan()
|
||||
go func() { srv.Serve(l) }()
|
||||
c <- os.Interrupt
|
||||
<-stop
|
||||
|
||||
if called != true {
|
||||
t.Fatal("Expected LogFunc to be called.")
|
||||
}
|
||||
}
|
||||
|
||||
// SyncBuffer calls Done on the embedded wait group after each call to Write.
|
||||
type SyncBuffer struct {
|
||||
*sync.WaitGroup
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (buf *SyncBuffer) Write(b []byte) (int, error) {
|
||||
defer buf.Done()
|
||||
return buf.Buffer.Write(b)
|
||||
}
|
||||
125
vendor/github.com/tylerb/graceful/http2_test.go
сгенерированный
поставляемый
Обычный файл
125
vendor/github.com/tylerb/graceful/http2_test.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,125 @@
|
||||
// +build go1.6
|
||||
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func createServer() *http.Server {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
server := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: mux}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func checkIfConnectionToServerIsHTTP2(t *testing.T, wg *sync.WaitGroup, c chan os.Signal) {
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
err := http2.ConfigureTransport(tr)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Unable to upgrade client transport to HTTP/2")
|
||||
}
|
||||
|
||||
client := http.Client{Transport: tr}
|
||||
r, err := client.Get(fmt.Sprintf("https://localhost:%d", port))
|
||||
|
||||
c <- os.Interrupt
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error encountered while connecting to test server: %s", err)
|
||||
}
|
||||
|
||||
if !r.ProtoAtLeast(2, 0) {
|
||||
t.Fatalf("Expected HTTP/2 connection to server, but connection was using %s", r.Proto)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP2ListenAndServeTLS(t *testing.T) {
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
server := createServer()
|
||||
|
||||
var srv *Server
|
||||
go func() {
|
||||
// set timeout of 0 to test idle connection closing
|
||||
srv = &Server{Timeout: 0, TCPKeepAlive: 1 * time.Minute, Server: server, interrupt: c}
|
||||
srv.ListenAndServeTLS("test-fixtures/cert.crt", "test-fixtures/key.pem")
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
time.Sleep(waitTime) // Wait for the server to start
|
||||
|
||||
wg.Add(1)
|
||||
go checkIfConnectionToServerIsHTTP2(t, &wg, c)
|
||||
wg.Wait()
|
||||
|
||||
c <- os.Interrupt // kill the server to close idle connections
|
||||
|
||||
// block on the stopChan until the server has shut down
|
||||
select {
|
||||
case <-srv.StopChan():
|
||||
case <-time.After(killTime * 2):
|
||||
t.Fatal("Timed out while waiting for explicit stop to complete")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestHTTP2ListenAndServeTLSConfig(t *testing.T) {
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
|
||||
server2 := createServer()
|
||||
|
||||
go func() {
|
||||
srv := &Server{Timeout: killTime, TCPKeepAlive: 1 * time.Minute, Server: server2, interrupt: c}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair("test-fixtures/cert.crt", "test-fixtures/key.pem")
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{"h2"}, // We need to explicitly enable http/2 in Go 1.7+
|
||||
}
|
||||
|
||||
tlsConf.BuildNameToCertificate()
|
||||
|
||||
srv.ListenAndServeTLSConfig(tlsConf)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
time.Sleep(waitTime) // Wait for the server to start
|
||||
|
||||
wg.Add(1)
|
||||
go checkIfConnectionToServerIsHTTP2(t, &wg, c)
|
||||
wg.Wait()
|
||||
}
|
||||
32
vendor/github.com/tylerb/graceful/keepalive_listener.go
сгенерированный
поставляемый
Обычный файл
32
vendor/github.com/tylerb/graceful/keepalive_listener.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,32 @@
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type keepAliveConn interface {
|
||||
SetKeepAlive(bool) error
|
||||
SetKeepAlivePeriod(d time.Duration) error
|
||||
}
|
||||
|
||||
// keepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away.
|
||||
type keepAliveListener struct {
|
||||
net.Listener
|
||||
keepAlivePeriod time.Duration
|
||||
}
|
||||
|
||||
func (ln keepAliveListener) Accept() (net.Conn, error) {
|
||||
c, err := ln.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kac := c.(keepAliveConn)
|
||||
kac.SetKeepAlive(true)
|
||||
kac.SetKeepAlivePeriod(ln.keepAlivePeriod)
|
||||
return c, nil
|
||||
}
|
||||
77
vendor/github.com/tylerb/graceful/limit_listen.go
сгенерированный
поставляемый
Обычный файл
77
vendor/github.com/tylerb/graceful/limit_listen.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,77 @@
|
||||
// Copyright 2013 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrNotTCP indicates that network connection is not a TCP connection.
|
||||
var ErrNotTCP = errors.New("only tcp connections have keepalive")
|
||||
|
||||
// LimitListener returns a Listener that accepts at most n simultaneous
|
||||
// connections from the provided Listener.
|
||||
func LimitListener(l net.Listener, n int) net.Listener {
|
||||
return &limitListener{l, make(chan struct{}, n)}
|
||||
}
|
||||
|
||||
type limitListener struct {
|
||||
net.Listener
|
||||
sem chan struct{}
|
||||
}
|
||||
|
||||
func (l *limitListener) acquire() { l.sem <- struct{}{} }
|
||||
func (l *limitListener) release() { <-l.sem }
|
||||
|
||||
func (l *limitListener) Accept() (net.Conn, error) {
|
||||
l.acquire()
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
l.release()
|
||||
return nil, err
|
||||
}
|
||||
return &limitListenerConn{Conn: c, release: l.release}, nil
|
||||
}
|
||||
|
||||
type limitListenerConn struct {
|
||||
net.Conn
|
||||
releaseOnce sync.Once
|
||||
release func()
|
||||
}
|
||||
|
||||
func (l *limitListenerConn) Close() error {
|
||||
err := l.Conn.Close()
|
||||
l.releaseOnce.Do(l.release)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *limitListenerConn) SetKeepAlive(doKeepAlive bool) error {
|
||||
tcpc, ok := l.Conn.(*net.TCPConn)
|
||||
if !ok {
|
||||
return ErrNotTCP
|
||||
}
|
||||
return tcpc.SetKeepAlive(doKeepAlive)
|
||||
}
|
||||
|
||||
func (l *limitListenerConn) SetKeepAlivePeriod(d time.Duration) error {
|
||||
tcpc, ok := l.Conn.(*net.TCPConn)
|
||||
if !ok {
|
||||
return ErrNotTCP
|
||||
}
|
||||
return tcpc.SetKeepAlivePeriod(d)
|
||||
}
|
||||
43
vendor/github.com/tylerb/graceful/test-fixtures/cert.crt
сгенерированный
поставляемый
Обычный файл
43
vendor/github.com/tylerb/graceful/test-fixtures/cert.crt
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,43 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDhTCCAm2gAwIBAgIUDvdWhjUd/JS+E5bxZlmCM+giGHMwDQYJKoZIhvcNAQEL
|
||||
BQAwHzEdMBsGA1UEAxMUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcNMTYwNjAyMDMy
|
||||
MjA0WhcNMTkwNjAyMDMyMjM0WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDoyMTUK2OSp+XhKRXB/+uO6YAJE/W
|
||||
2rzqARahWT6boHZMDhHXRtdwYxWwiUqoxlEeBrEerQ2qPFAqlWkDw8zliE/DWgXg
|
||||
BiW+Vq5DAn3F1jZ5WskLWr1iP48oK4/l+BXEsDd44MHZFoSZiWlr2Fi4iaIHJE7+
|
||||
LGBqPVQXwBYTyc7Jvi3HY8I4/waaAwXoSo8vDPjRiMCD2wlg24Rimocf4goa/2Xs
|
||||
Z0NU76Uf2jPdsZ5MujjKRqwHDEAjiBq0aPvm6igkNGAGoZ6QYEptO+J4t1oFrbdP
|
||||
gYRlpqCa3ekr9gc+wg5AO/V9x8/cypbQ8tpwFwvvSYg2TJaUMZ5abc+HAgMBAAGj
|
||||
gcMwgcAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQC
|
||||
R0Y69NLOfFCLRiB5N3uoacILXTAfBgNVHSMEGDAWgBRm0fFHSXtDCVHC8UW7/obv
|
||||
DLp9tTBJBggrBgEFBQcBAQQ9MDswOQYIKwYBBQUHMAKGLWh0dHA6Ly9sb2NhbGhv
|
||||
c3Qvc2VsZi1pc3N1ZWQtaW50ZXJtZWRpYXRlLmNydDAUBgNVHREEDTALgglsb2Nh
|
||||
bGhvc3QwDQYJKoZIhvcNAQELBQADggEBALAf/nowwB0NJ7lGGaoVKhmMHxBEQkd1
|
||||
K/jBAlJg9Kgmg1IJJ7zLE3SeYF8tGTNYATd4RLmqo1GakrMDaKWNXd74v3p/tWmb
|
||||
4vqCh6WzFPHU1dpxDKtbbmaLt9Ije7s6DuQAz9bBXM0mN0vy5F0dORpx/j0h3u1B
|
||||
j7B5O8kLejPY2w/8pd+QECCb1Q5A6Xx1EEsJpzTlGXO0SBla/oCg+nvirsBGVpWr
|
||||
bGskAIwG9wNKuGfg4m5u1bL87iX80NemeLtWRWVM+Ry/RhfOokH59/EIFRAXeRz6
|
||||
gXjIWa0vcXnhW1MOvbD1GFYhO6AJAnDwWes48WfBHysOhq0RycdpGw0=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDjTCCAnWgAwIBAgIUMzpit8+j2dWxdk1PdMqGWYalZyIwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMVGVzdCBSb290IENBMB4XDTE2MDUyOTEwNDYwMFoXDTMx
|
||||
MDUyNjEwNDYzMFowHzEdMBsGA1UEAxMUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDs6kY6mHJWzupq5dsSavPZHuv6
|
||||
0E9PczHbujWLuzv7+qbwzcAgfRvaeR0xgvf7q9pjMgJ7/kNANgneWGpwciLgHtiJ
|
||||
rSHii3RZfWlK4gdbCXya9EmHj8zO+9xGBHM0FrqfqA+IA70SimFcwGPrGHyERsdX
|
||||
+mqO64Z95yI5uJpoS8OBAUPU8i6xvNLZGmgUEF3CRhDDTYVGcTEtKAPcnnBuZzZU
|
||||
Ds+DrHf/MC7HHK0/l0auuRz3p+/GFNePGePG+FFbInS/vwHwrkMW2tzBKG41K+gD
|
||||
GfkTjVU8xBSiMYOiEja6YcJ4GuzEPcmu5LS+6BkLlsIbazDW5IM8p+7+8RKjAgMB
|
||||
AAGjgcgwgcUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
|
||||
BBYEFGbR8UdJe0MJUcLxRbv+hu8Mun21MB8GA1UdIwQYMBaAFKmz0h3CW1HBO9uz
|
||||
uCzg+MNPGZtkMEEGCCsGAQUFBwEBBDUwMzAxBggrBgEFBQcwAoYlaHR0cDovL2xv
|
||||
Y2FsaG9zdC9zZWxmLWlzc3VlZC1yb290LmNydDAfBgNVHREEGDAWghRUZXN0IElu
|
||||
dGVybWVkaWF0ZSBDQTANBgkqhkiG9w0BAQsFAAOCAQEAaYVGqHbaE0c9F/kyIMgu
|
||||
S3HuNn4pBh2EwGcKIlPkDe43hqXjhS/+itmWk75rQz+Rw+acevGoxbpDR38abTIS
|
||||
RJd9L/3MA644z8F82er3pNjKqvS/vTre/wsvGYwmEM+GrgJw3HUcisc93qLgaWH2
|
||||
kjky208k9kOuzJDiY45eu9TfSSmjSHSMCtxk8p5wYKDcfVz+uqlBhVEiHGjQIc2E
|
||||
66SituusiwgQv/mdtEW7y48EvMGdzxPfLFcvj06B3vTsZaaYyB6GyKwMcaPFvHRr
|
||||
V0yYaKRZgAh4X6LHlgPJqvIv3gjMdJR55durAO7tI9Pos0o5Lv5WJgi0g0KvMsco
|
||||
qQ==
|
||||
-----END CERTIFICATE-----
|
||||
27
vendor/github.com/tylerb/graceful/test-fixtures/key.pem
сгенерированный
поставляемый
Обычный файл
27
vendor/github.com/tylerb/graceful/test-fixtures/key.pem
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAw6MjE1Ctjkqfl4SkVwf/rjumACRP1tq86gEWoVk+m6B2TA4R
|
||||
10bXcGMVsIlKqMZRHgaxHq0NqjxQKpVpA8PM5YhPw1oF4AYlvlauQwJ9xdY2eVrJ
|
||||
C1q9Yj+PKCuP5fgVxLA3eODB2RaEmYlpa9hYuImiByRO/ixgaj1UF8AWE8nOyb4t
|
||||
x2PCOP8GmgMF6EqPLwz40YjAg9sJYNuEYpqHH+IKGv9l7GdDVO+lH9oz3bGeTLo4
|
||||
ykasBwxAI4gatGj75uooJDRgBqGekGBKbTvieLdaBa23T4GEZaagmt3pK/YHPsIO
|
||||
QDv1fcfP3MqW0PLacBcL70mINkyWlDGeWm3PhwIDAQABAoIBAQC87HWa2XZAyt+D
|
||||
OpxZT2ghoYiU6nwPR/zXHWX1OnGzaCnVGGEyOz8hUQ5JBMwMYDdFf8DbltJzavsf
|
||||
pFldQWBE6HXeeLjjtgwM2zg9jdJXkp3YY0tyo5XvouFkMW0s735WCrYHDUUllxFG
|
||||
E+SyOKK00nSd4PpHiiMxdTgYF286exwOpzjhcJfAkn7oBNeOGc5VLOvcvakrSrdq
|
||||
OYBAJ25HSVFnSQbeAAsCzBEBZC0WLyB1BQGcidbtEn8sxyGnV8HWjbXY+MJQWHg+
|
||||
q2iK+uvO4wtrE/WC6p4Ty44Myh+AB79s35HWKYd4okwKkpI1QdD543TIiZnkNEVI
|
||||
aS/uH13BAoGBAP/psBxKzIft59hw+U9NscH6N9/ze8iAtOtqsWdER/qXCrlUn8+j
|
||||
F/xquJR6gDj5GwGBt07asEuoG8CKJMQI0c3AeHF7XBcmUunBStktb9O97Zsp6bNJ
|
||||
olsrWlM4yvVuCVizEwIYjHrMBOS3YIPErM1LmAyDHmzx3+yz+3+WxRQLAoGBAMO0
|
||||
MaJDPisMC05pvieHRb91HlsiSrASeMkw1FmHI0b/gcC88mEnuXIze1ySoF6FE7B7
|
||||
xaEm6Lf5Snl0JgXPDSj6ukd51NdaU2VmpKvDOrvQ5QQE9mXaDkXv/i2B0YkCh+Hy
|
||||
bkziW1IKnWT2PTRAAEIJQ22oK51MdQnvCdmtsIP1AoGBAKnMiEl9Z9AZDmgSLZls
|
||||
17D5MPGrQEp8+43oMOVv7MJcTYVCnPbMJDIbLXV3AnTK9Bw/0TzE5YyNcjyCbHqV
|
||||
z39RYZkKXMQPbZwj4GHRQA2iS3FUkfeft9X+IeRuHlxSMmlkCAyv9SXVELog4i0L
|
||||
5gwhSDWlGh73LbiEgy7Y/tKZAoGBALTiMhYGDMoA4dpiBi3G7AKgH6SgN2QyTo22
|
||||
oi71pveSZb1dZrHB47fYOadApxV17tLqM6pVqjeRJPLJFfO8gi9kPxSdWMqLZBWP
|
||||
H5jaY8kAtQxYAd32A8dEoSwylxcJzcpbJvPNLBbSVNPifIN0vEhNA5OxIk7LQkoi
|
||||
NHqL/WCZAoGAPf3kb9Gw/NkBq4Cn86pQfP/xE0h7zcoNmFtLbdKIjId+DDDOPOeX
|
||||
9tm33fZzw0SG4KlRQlsqgzFvm8aDD8rpW17341Z/rWlLo8uHNdRkMvbSabc34vPv
|
||||
4lrs0rHSYW06MlqkJBNVraySRz7hmU4+n7YMvNI0Due9mVGmE1NU/vI=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
40
vendor/github.com/tylerb/graceful/tests/main.go
сгенерированный
поставляемый
Обычный файл
40
vendor/github.com/tylerb/graceful/tests/main.go
сгенерированный
поставляемый
Обычный файл
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/urfave/negroni"
|
||||
"gopkg.in/tylerb/graceful.v1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
n := negroni.New()
|
||||
fmt.Println("Launching server on :3000")
|
||||
graceful.Run(":3000", 0, n)
|
||||
fmt.Println("Terminated server on :3000")
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
n := negroni.New()
|
||||
fmt.Println("Launching server on :3001")
|
||||
graceful.Run(":3001", 0, n)
|
||||
fmt.Println("Terminated server on :3001")
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
n := negroni.New()
|
||||
fmt.Println("Launching server on :3002")
|
||||
graceful.Run(":3002", 0, n)
|
||||
fmt.Println("Terminated server on :3002")
|
||||
wg.Done()
|
||||
}()
|
||||
fmt.Println("Press ctrl+c. All servers should terminate.")
|
||||
wg.Wait()
|
||||
|
||||
}
|
||||
Ссылка в новой задаче
Block a user