initial commit

This commit is contained in:
ginuerzh
2022-03-14 20:27:14 +08:00
commit 9397cb5351
175 changed files with 16196 additions and 0 deletions

54
listener/http2/conn.go Normal file
View File

@ -0,0 +1,54 @@
package http2
import (
"errors"
"net"
"net/http"
"time"
)
// a dummy HTTP2 server conn used by HTTP2 handler
type conn struct {
r *http.Request
w http.ResponseWriter
closed chan struct{}
}
func (c *conn) Read(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "read", Net: "http2", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}
func (c *conn) Write(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "write", Net: "http2", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}
func (c *conn) Close() error {
select {
case <-c.closed:
default:
close(c.closed)
}
return nil
}
func (c *conn) LocalAddr() net.Addr {
addr, _ := net.ResolveTCPAddr("tcp", c.r.Host)
return addr
}
func (c *conn) RemoteAddr() net.Addr {
addr, _ := net.ResolveTCPAddr("tcp", c.r.RemoteAddr)
return addr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}

89
listener/http2/h2/conn.go Normal file
View File

@ -0,0 +1,89 @@
package h2
import (
"errors"
"io"
"net"
"net/http"
"time"
)
// HTTP2 connection, wrapped up just like a net.Conn
type conn struct {
r io.Reader
w io.Writer
remoteAddr net.Addr
localAddr net.Addr
closed chan struct{}
}
func (c *conn) Read(b []byte) (n int, err error) {
return c.r.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
return c.w.Write(b)
}
func (c *conn) Close() (err error) {
select {
case <-c.closed:
return
default:
close(c.closed)
}
if rc, ok := c.r.(io.Closer); ok {
err = rc.Close()
}
if w, ok := c.w.(io.Closer); ok {
err = w.Close()
}
return
}
func (c *conn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *conn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
type flushWriter struct {
w io.Writer
}
func (fw flushWriter) Write(p []byte) (n int, err error) {
defer func() {
if r := recover(); r != nil {
if s, ok := r.(string); ok {
err = errors.New(s)
// log.Log("[http2]", err)
return
}
err = r.(error)
}
}()
n, err = fw.w.Write(p)
if err != nil {
// log.Log("flush writer:", err)
return
}
if f, ok := fw.w.(http.Flusher); ok {
f.Flush()
}
return
}

View File

@ -0,0 +1,178 @@
package h2
import (
"crypto/tls"
"errors"
"net"
"net/http"
"net/http/httputil"
"github.com/go-gost/gost/v3/pkg/common/admission"
"github.com/go-gost/gost/v3/pkg/common/metrics"
"github.com/go-gost/gost/v3/pkg/listener"
"github.com/go-gost/gost/v3/pkg/logger"
md "github.com/go-gost/gost/v3/pkg/metadata"
"github.com/go-gost/gost/v3/pkg/registry"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
func init() {
registry.ListenerRegistry().Register("h2c", NewListener)
registry.ListenerRegistry().Register("h2", NewTLSListener)
}
type h2Listener struct {
server *http.Server
addr net.Addr
cqueue chan net.Conn
errChan chan error
logger logger.Logger
md metadata
h2c bool
options listener.Options
}
func NewListener(opts ...listener.Option) listener.Listener {
options := listener.Options{}
for _, opt := range opts {
opt(&options)
}
return &h2Listener{
h2c: true,
logger: options.Logger,
options: options,
}
}
func NewTLSListener(opts ...listener.Option) listener.Listener {
options := listener.Options{}
for _, opt := range opts {
opt(&options)
}
return &h2Listener{
logger: options.Logger,
options: options,
}
}
func (l *h2Listener) Init(md md.Metadata) (err error) {
if err = l.parseMetadata(md); err != nil {
return
}
l.server = &http.Server{
Addr: l.options.Addr,
}
ln, err := net.Listen("tcp", l.options.Addr)
if err != nil {
return err
}
l.addr = ln.Addr()
ln = metrics.WrapListener(l.options.Service, ln)
ln = admission.WrapListener(l.options.Admission, ln)
if l.h2c {
l.server.Handler = h2c.NewHandler(
http.HandlerFunc(l.handleFunc), &http2.Server{})
} else {
l.server.Handler = http.HandlerFunc(l.handleFunc)
l.server.TLSConfig = l.options.TLSConfig
if err := http2.ConfigureServer(l.server, nil); err != nil {
ln.Close()
return err
}
ln = tls.NewListener(ln, l.options.TLSConfig)
}
l.cqueue = make(chan net.Conn, l.md.backlog)
l.errChan = make(chan error, 1)
go func() {
if err := l.server.Serve(ln); err != nil {
l.logger.Error(err)
}
}()
return
}
func (l *h2Listener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.cqueue:
case err, ok = <-l.errChan:
if !ok {
err = listener.ErrClosed
}
}
return
}
func (l *h2Listener) Addr() net.Addr {
return l.addr
}
func (l *h2Listener) Close() (err error) {
select {
case <-l.errChan:
default:
err = l.server.Close()
l.errChan <- err
close(l.errChan)
}
return nil
}
func (l *h2Listener) handleFunc(w http.ResponseWriter, r *http.Request) {
if l.logger.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpRequest(r, false)
l.logger.Debug(string(dump))
}
conn, err := l.upgrade(w, r)
if err != nil {
l.logger.Error(err)
return
}
select {
case l.cqueue <- conn:
default:
conn.Close()
l.logger.Warnf("connection queue is full, client %s discarded", r.RemoteAddr)
}
<-conn.closed // NOTE: we need to wait for streaming end, or the connection will be closed
}
func (l *h2Listener) upgrade(w http.ResponseWriter, r *http.Request) (*conn, error) {
if l.md.path == "" && r.Method != http.MethodConnect {
w.WriteHeader(http.StatusMethodNotAllowed)
return nil, errors.New("method not allowed")
}
if l.md.path != "" && r.RequestURI != l.md.path {
w.WriteHeader(http.StatusBadRequest)
return nil, errors.New("bad request")
}
w.WriteHeader(http.StatusOK)
if fw, ok := w.(http.Flusher); ok {
fw.Flush() // write header to client
}
remoteAddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if remoteAddr == nil {
remoteAddr = &net.TCPAddr{
IP: net.IPv4zero,
Port: 0,
}
}
return &conn{
r: r.Body,
w: flushWriter{w},
localAddr: l.addr,
remoteAddr: remoteAddr,
closed: make(chan struct{}),
}, nil
}

View File

@ -0,0 +1,29 @@
package h2
import (
mdata "github.com/go-gost/gost/v3/pkg/metadata"
)
const (
defaultBacklog = 128
)
type metadata struct {
path string
backlog int
}
func (l *h2Listener) parseMetadata(md mdata.Metadata) (err error) {
const (
path = "path"
backlog = "backlog"
)
l.md.backlog = mdata.GetInt(md, backlog)
if l.md.backlog <= 0 {
l.md.backlog = defaultBacklog
}
l.md.path = mdata.GetString(md, path)
return
}

120
listener/http2/listener.go Normal file
View File

@ -0,0 +1,120 @@
package http2
import (
"crypto/tls"
"net"
"net/http"
"github.com/go-gost/gost/v3/pkg/common/admission"
"github.com/go-gost/gost/v3/pkg/common/metrics"
"github.com/go-gost/gost/v3/pkg/listener"
"github.com/go-gost/gost/v3/pkg/logger"
md "github.com/go-gost/gost/v3/pkg/metadata"
"github.com/go-gost/gost/v3/pkg/registry"
http2_util "github.com/go-gost/x/internal/util/http2"
"golang.org/x/net/http2"
)
func init() {
registry.ListenerRegistry().Register("http2", NewListener)
}
type http2Listener struct {
server *http.Server
addr net.Addr
cqueue chan net.Conn
errChan chan error
logger logger.Logger
md metadata
options listener.Options
}
func NewListener(opts ...listener.Option) listener.Listener {
options := listener.Options{}
for _, opt := range opts {
opt(&options)
}
return &http2Listener{
logger: options.Logger,
options: options,
}
}
func (l *http2Listener) Init(md md.Metadata) (err error) {
if err = l.parseMetadata(md); err != nil {
return
}
l.server = &http.Server{
Addr: l.options.Addr,
Handler: http.HandlerFunc(l.handleFunc),
TLSConfig: l.options.TLSConfig,
}
if err := http2.ConfigureServer(l.server, nil); err != nil {
return err
}
ln, err := net.Listen("tcp", l.options.Addr)
if err != nil {
return err
}
l.addr = ln.Addr()
ln = metrics.WrapListener(l.options.Service, ln)
ln = admission.WrapListener(l.options.Admission, ln)
ln = tls.NewListener(
ln,
l.options.TLSConfig,
)
l.cqueue = make(chan net.Conn, l.md.backlog)
l.errChan = make(chan error, 1)
go func() {
if err := l.server.Serve(ln); err != nil {
l.logger.Error(err)
}
}()
return
}
func (l *http2Listener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.cqueue:
case err, ok = <-l.errChan:
if !ok {
err = listener.ErrClosed
}
}
return
}
func (l *http2Listener) Addr() net.Addr {
return l.addr
}
func (l *http2Listener) Close() (err error) {
select {
case <-l.errChan:
default:
err = l.server.Close()
l.errChan <- err
close(l.errChan)
}
return nil
}
func (l *http2Listener) handleFunc(w http.ResponseWriter, r *http.Request) {
raddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr)
conn := http2_util.NewServerConn(w, r, l.addr, raddr)
select {
case l.cqueue <- conn:
default:
l.logger.Warnf("connection queue is full, client %s discarded", r.RemoteAddr)
return
}
<-conn.Done()
}

View File

@ -0,0 +1,25 @@
package http2
import (
mdata "github.com/go-gost/gost/v3/pkg/metadata"
)
const (
defaultBacklog = 128
)
type metadata struct {
backlog int
}
func (l *http2Listener) parseMetadata(md mdata.Metadata) (err error) {
const (
backlog = "backlog"
)
l.md.backlog = mdata.GetInt(md, backlog)
if l.md.backlog <= 0 {
l.md.backlog = defaultBacklog
}
return
}