Server http and https on the same port using a customized server (#2247)
parent
6c2fb19ed7
commit
b0b919a1d6
@ -0,0 +1,214 @@ |
||||
/* |
||||
* Minio Cloud Storage, (C) 2016 Minio, Inc. |
||||
* |
||||
* 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 main |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"io" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"strings" |
||||
) |
||||
|
||||
var defaultHTTP2Methods = []string{ |
||||
"PRI", |
||||
} |
||||
|
||||
var defaultHTTP1Methods = []string{ |
||||
"OPTIONS", |
||||
"GET", |
||||
"HEAD", |
||||
"POST", |
||||
"PUT", |
||||
"DELETE", |
||||
"TRACE", |
||||
"CONNECT", |
||||
} |
||||
|
||||
// ConnBuf - contains network buffer to record data
|
||||
type ConnBuf struct { |
||||
buffer []byte |
||||
unRead bool |
||||
offset int |
||||
} |
||||
|
||||
// MuxConn - implements a Read() which streams twice the firs bytes from
|
||||
// the incoming connection, to help peeking protocol
|
||||
type MuxConn struct { |
||||
net.Conn |
||||
lastError error |
||||
dataBuf ConnBuf |
||||
} |
||||
|
||||
// NewMuxConn - creates a new MuxConn instance
|
||||
func NewMuxConn(c net.Conn) *MuxConn { |
||||
h1 := longestWord(defaultHTTP1Methods) |
||||
h2 := longestWord(defaultHTTP2Methods) |
||||
max := h1 |
||||
if h2 > max { |
||||
max = h2 |
||||
} |
||||
return &MuxConn{Conn: c, dataBuf: ConnBuf{buffer: make([]byte, max+1)}} |
||||
} |
||||
|
||||
// PeekProtocol - reads the first bytes, then checks if it is similar
|
||||
// to one of the default http methods
|
||||
func (c *MuxConn) PeekProtocol() string { |
||||
var n int |
||||
n, c.lastError = c.Conn.Read(c.dataBuf.buffer) |
||||
if n == 0 || (c.lastError != nil && c.lastError != io.EOF) { |
||||
return "" |
||||
} |
||||
c.dataBuf.unRead = true |
||||
for _, m := range defaultHTTP1Methods { |
||||
if strings.HasPrefix(string(c.dataBuf.buffer), m) { |
||||
return "http" |
||||
} |
||||
} |
||||
for _, m := range defaultHTTP2Methods { |
||||
if strings.HasPrefix(string(c.dataBuf.buffer), m) { |
||||
return "http2" |
||||
} |
||||
} |
||||
return "tls" |
||||
} |
||||
|
||||
// Read - streams the MuxConn buffer when reset flag is activated, otherwise
|
||||
// streams from the incoming network connection
|
||||
func (c *MuxConn) Read(b []byte) (int, error) { |
||||
if c.dataBuf.unRead { |
||||
n := copy(b, c.dataBuf.buffer[c.dataBuf.offset:]) |
||||
c.dataBuf.offset += n |
||||
if c.dataBuf.offset == len(c.dataBuf.buffer) { |
||||
// We finished copying all c.buffer, reset all
|
||||
c.dataBuf.unRead = false |
||||
c.dataBuf.offset = 0 |
||||
c.dataBuf.buffer = c.dataBuf.buffer[:] |
||||
if n < len(b) { |
||||
// Continue copying from socket if b still has room for data
|
||||
tmpBuffer := make([]byte, len(b)-n-1) |
||||
nr, err := c.Conn.Read(tmpBuffer) |
||||
for idx, val := range tmpBuffer { |
||||
b[n+idx] = val |
||||
} |
||||
return n + nr, err |
||||
} |
||||
} |
||||
// We here return the last error
|
||||
return n, c.lastError |
||||
} |
||||
return c.Conn.Read(b) |
||||
} |
||||
|
||||
// MuxListener - encapuslates the standard net.Listener to inspect
|
||||
// the communication protocol upon network connection
|
||||
type MuxListener struct { |
||||
net.Listener |
||||
config *tls.Config |
||||
} |
||||
|
||||
// NewMuxListener - creates new MuxListener, returns error when cert/key files are not found
|
||||
// or invalid
|
||||
func NewMuxListener(listener net.Listener, certPath, keyPath string) (MuxListener, error) { |
||||
var err error |
||||
config := &tls.Config{} |
||||
if config.NextProtos == nil { |
||||
config.NextProtos = []string{"http/1.1", "h2"} |
||||
} |
||||
config.Certificates = make([]tls.Certificate, 1) |
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certPath, keyPath) |
||||
if err != nil { |
||||
return MuxListener{}, err |
||||
} |
||||
return MuxListener{Listener: listener, config: config}, nil |
||||
} |
||||
|
||||
// Accept - peek the protocol to decide if we should wrap the
|
||||
// network stream with the TLS server
|
||||
func (l MuxListener) Accept() (net.Conn, error) { |
||||
c, err := l.Listener.Accept() |
||||
if err != nil { |
||||
return c, err |
||||
} |
||||
cmux := NewMuxConn(c) |
||||
protocol := cmux.PeekProtocol() |
||||
if protocol == "tls" { |
||||
return tls.Server(cmux, l.config), nil |
||||
} |
||||
return cmux, nil |
||||
} |
||||
|
||||
// MuxServer - the main mux server
|
||||
type MuxServer struct { |
||||
http.Server |
||||
} |
||||
|
||||
// ListenAndServeTLS - similar to the http.Server version. However, it has the
|
||||
// ability to redirect http requests to the correct HTTPS url if the client
|
||||
// mistakenly initiates a http connection over the https port
|
||||
func (m *MuxServer) ListenAndServeTLS(certFile, keyFile string) error { |
||||
listener, err := net.Listen("tcp", m.Server.Addr) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
mux, err := NewMuxListener(listener, mustGetCertFile(), mustGetKeyFile()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
err = http.Serve(mux, |
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
// We reach here when MuxListener.MuxConn is not wrapped with tls.Server
|
||||
if r.TLS == nil { |
||||
u := url.URL{ |
||||
Scheme: "https", |
||||
Opaque: r.URL.Opaque, |
||||
User: r.URL.User, |
||||
Host: r.Host, |
||||
Path: r.URL.Path, |
||||
RawQuery: r.URL.RawQuery, |
||||
Fragment: r.URL.Fragment, |
||||
} |
||||
http.Redirect(w, r, u.String(), http.StatusMovedPermanently) |
||||
} else { |
||||
// Execute registered handlers
|
||||
m.Server.Handler.ServeHTTP(w, r) |
||||
} |
||||
})) |
||||
return err |
||||
} |
||||
|
||||
// ListenAndServe - Same as the http.Server version
|
||||
func (m *MuxServer) ListenAndServe() error { |
||||
return m.Server.ListenAndServe() |
||||
} |
||||
|
||||
func longestWord(strings []string) int { |
||||
maxLen := 0 |
||||
for _, m := range defaultHTTP1Methods { |
||||
if maxLen < len(m) { |
||||
maxLen = len(m) |
||||
} |
||||
} |
||||
for _, m := range defaultHTTP2Methods { |
||||
if maxLen < len(m) { |
||||
maxLen = len(m) |
||||
} |
||||
} |
||||
|
||||
return maxLen |
||||
} |
Loading…
Reference in new issue