mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
webconsole: support websocket proxy
This commit is contained in:
10
Gopkg.lock
generated
10
Gopkg.lock
generated
@@ -800,6 +800,14 @@
|
||||
revision = "1624edc4454b8682399def8740d46db5e4362ba4"
|
||||
version = "v1.1.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:81abe06bccf5e35235e8d6387508a4f3417393216e62bd4c2e383bbebd4b4fbb"
|
||||
name = "github.com/koding/websocketproxy"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7ed82d81a28c9ba1ed6fd1157ce714760f214c98"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0a69a1c0db3591fcefb47f115b224592c8dfa4368b7ba9fae509d5e16cdc95c8"
|
||||
name = "github.com/konsorten/go-windows-terminal-sequences"
|
||||
@@ -1749,6 +1757,7 @@
|
||||
"github.com/anacrolix/torrent",
|
||||
"github.com/anacrolix/torrent/bencode",
|
||||
"github.com/anacrolix/torrent/metainfo",
|
||||
"github.com/anacrolix/torrent/storage",
|
||||
"github.com/aokoli/goutils",
|
||||
"github.com/aws/aws-sdk-go/aws",
|
||||
"github.com/aws/aws-sdk-go/aws/credentials",
|
||||
@@ -1794,6 +1803,7 @@
|
||||
"github.com/jinzhu/gorm",
|
||||
"github.com/jinzhu/gorm/dialects/mysql",
|
||||
"github.com/json-iterator/go",
|
||||
"github.com/koding/websocketproxy",
|
||||
"github.com/kr/pty",
|
||||
"github.com/mholt/caddy",
|
||||
"github.com/mholt/caddy/startupshutdown",
|
||||
|
||||
@@ -92,6 +92,10 @@
|
||||
name = "github.com/gorilla/websocket"
|
||||
version = "1.4.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/koding/websocketproxy"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/gosuri/uitable"
|
||||
|
||||
@@ -190,7 +190,7 @@ func (self *SHoststorage) getExtraDetails(extra *jsonutils.JSONDict) *jsonutils.
|
||||
wasted := storage.GetUsedCapacity(tristate.False)
|
||||
extra.Add(jsonutils.NewInt(int64(used)), "used_capacity")
|
||||
extra.Add(jsonutils.NewInt(int64(wasted)), "waste_capacity")
|
||||
extra.Add(jsonutils.NewInt(int64(storage.Capacity-used-wasted)), "free_capacity")
|
||||
extra.Add(jsonutils.NewInt(int64(storage.GetFreeCapacity())), "free_capacity")
|
||||
extra.Add(jsonutils.NewString(storage.StorageType), "storage_type")
|
||||
extra.Add(jsonutils.NewString(storage.MediumType), "medium_type")
|
||||
extra.Add(jsonutils.NewBool(storage.Enabled), "enabled")
|
||||
|
||||
@@ -22,9 +22,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ApiPathPrefix = "/webconsole/"
|
||||
ConnectPathPrefix = "/connect/"
|
||||
WebsockifyPathPrefix = "/websockify/"
|
||||
ApiPathPrefix = "/webconsole/"
|
||||
ConnectPathPrefix = "/connect/"
|
||||
WebsockifyPathPrefix = "/websockify/"
|
||||
WebsocketProxyPathPrefix = "/wsproxy/"
|
||||
)
|
||||
|
||||
func InitHandlers(app *appsrv.Application) {
|
||||
|
||||
@@ -37,8 +37,10 @@ func (s *ConnectionServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
var srv http.Handler
|
||||
protocol := sessionObj.GetProtocol()
|
||||
switch protocol {
|
||||
case session.VNC, session.SPICE, session.WMKS:
|
||||
case session.VNC, session.SPICE:
|
||||
srv, err = NewWebsockifyServer(sessionObj)
|
||||
case session.WMKS:
|
||||
srv, err = NewWebsocketProxyServer(sessionObj)
|
||||
default:
|
||||
srv, err = NewTTYServer(sessionObj)
|
||||
}
|
||||
|
||||
50
pkg/webconsole/server/websocketproxy_server.go
Normal file
50
pkg/webconsole/server/websocketproxy_server.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/koding/websocketproxy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/webconsole/session"
|
||||
)
|
||||
|
||||
type WebsocketProxyServer struct {
|
||||
Session *session.SSession
|
||||
proxy *websocketproxy.WebsocketProxy
|
||||
}
|
||||
|
||||
func NewWebsocketProxyServer(s *session.SSession) (*WebsocketProxyServer, error) {
|
||||
info := s.ISessionData.(*session.RemoteConsoleInfo)
|
||||
if info.Url == "" {
|
||||
return nil, fmt.Errorf("Empty proxy url")
|
||||
}
|
||||
u, err := url.Parse(info.Url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parse url %s: %v", info.Url, err)
|
||||
}
|
||||
proxySrv := websocketproxy.NewProxy(u)
|
||||
proxySrv.Dialer = &websocket.Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
HandshakeTimeout: 45 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
proxySrv.Backend = func(_ *http.Request) *url.URL {
|
||||
return u
|
||||
}
|
||||
proxySrv.Upgrader = &upgrader
|
||||
return &WebsocketProxyServer{
|
||||
Session: s,
|
||||
proxy: proxySrv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *WebsocketProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -58,11 +58,15 @@ func start() {
|
||||
// api handler
|
||||
root.PathPrefix(webconsole.ApiPathPrefix).Handler(app)
|
||||
|
||||
srv := server.NewConnectionServer()
|
||||
// websocket command text console handler
|
||||
root.Handle(webconsole.ConnectPathPrefix, server.NewConnectionServer())
|
||||
root.Handle(webconsole.ConnectPathPrefix, srv)
|
||||
|
||||
// websockify graphic console handler
|
||||
root.Handle(webconsole.WebsockifyPathPrefix, server.NewConnectionServer())
|
||||
root.Handle(webconsole.WebsockifyPathPrefix, srv)
|
||||
|
||||
// websocketproxy handler
|
||||
root.Handle(webconsole.WebsocketProxyPathPrefix, srv)
|
||||
|
||||
addr := net.JoinHostPort(o.Options.Address, strconv.Itoa(o.Options.Port))
|
||||
log.Infof("Start listen on %s", addr)
|
||||
|
||||
2
vendor/github.com/koding/websocketproxy/.travis.yml
generated
vendored
Normal file
2
vendor/github.com/koding/websocketproxy/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
language: go
|
||||
go: 1.8
|
||||
20
vendor/github.com/koding/websocketproxy/LICENSE.md
generated
vendored
Normal file
20
vendor/github.com/koding/websocketproxy/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Koding, Inc.
|
||||
|
||||
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.
|
||||
54
vendor/github.com/koding/websocketproxy/README.md
generated
vendored
Normal file
54
vendor/github.com/koding/websocketproxy/README.md
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# WebsocketProxy [](https://godoc.org/github.com/koding/websocketproxy) [](https://travis-ci.org/koding/websocketproxy)
|
||||
|
||||
WebsocketProxy is an http.Handler interface build on top of
|
||||
[gorilla/websocket](https://github.com/gorilla/websocket) that you can plug
|
||||
into your existing Go webserver to provide WebSocket reverse proxy.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/koding/websocketproxy
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
Below is a simple server that proxies to the given backend URL
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/koding/websocketproxy"
|
||||
)
|
||||
|
||||
var (
|
||||
flagBackend = flag.String("backend", "", "Backend URL for proxying")
|
||||
)
|
||||
|
||||
func main() {
|
||||
u, err := url.Parse(*flagBackend)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = http.ListenAndServe(":80", websocketproxy.NewProxy(u))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Save it as `proxy.go` and run as:
|
||||
|
||||
```bash
|
||||
go run proxy.go -backend ws://example.com:3000
|
||||
```
|
||||
|
||||
Now all incoming WebSocket requests coming to this server will be proxied to
|
||||
`ws://example.com:3000`
|
||||
|
||||
|
||||
233
vendor/github.com/koding/websocketproxy/websocketproxy.go
generated
vendored
Normal file
233
vendor/github.com/koding/websocketproxy/websocketproxy.go
generated
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
// Package websocketproxy is a reverse proxy for WebSocket connections.
|
||||
package websocketproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultUpgrader specifies the parameters for upgrading an HTTP
|
||||
// connection to a WebSocket connection.
|
||||
DefaultUpgrader = &websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
DefaultDialer = websocket.DefaultDialer
|
||||
)
|
||||
|
||||
// WebsocketProxy is an HTTP Handler that takes an incoming WebSocket
|
||||
// connection and proxies it to another server.
|
||||
type WebsocketProxy struct {
|
||||
// Director, if non-nil, is a function that may copy additional request
|
||||
// headers from the incoming WebSocket connection into the output headers
|
||||
// which will be forwarded to another server.
|
||||
Director func(incoming *http.Request, out http.Header)
|
||||
|
||||
// Backend returns the backend URL which the proxy uses to reverse proxy
|
||||
// the incoming WebSocket connection. Request is the initial incoming and
|
||||
// unmodified request.
|
||||
Backend func(*http.Request) *url.URL
|
||||
|
||||
// Upgrader specifies the parameters for upgrading a incoming HTTP
|
||||
// connection to a WebSocket connection. If nil, DefaultUpgrader is used.
|
||||
Upgrader *websocket.Upgrader
|
||||
|
||||
// Dialer contains options for connecting to the backend WebSocket server.
|
||||
// If nil, DefaultDialer is used.
|
||||
Dialer *websocket.Dialer
|
||||
}
|
||||
|
||||
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
||||
// request to the given target.
|
||||
func ProxyHandler(target *url.URL) http.Handler { return NewProxy(target) }
|
||||
|
||||
// NewProxy returns a new Websocket reverse proxy that rewrites the
|
||||
// URL's to the scheme, host and base path provider in target.
|
||||
func NewProxy(target *url.URL) *WebsocketProxy {
|
||||
backend := func(r *http.Request) *url.URL {
|
||||
// Shallow copy
|
||||
u := *target
|
||||
u.Fragment = r.URL.Fragment
|
||||
u.Path = r.URL.Path
|
||||
u.RawQuery = r.URL.RawQuery
|
||||
return &u
|
||||
}
|
||||
return &WebsocketProxy{Backend: backend}
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if w.Backend == nil {
|
||||
log.Println("websocketproxy: backend function is not defined")
|
||||
http.Error(rw, "internal server error (code: 1)", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
backendURL := w.Backend(req)
|
||||
if backendURL == nil {
|
||||
log.Println("websocketproxy: backend URL is nil")
|
||||
http.Error(rw, "internal server error (code: 2)", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
dialer := w.Dialer
|
||||
if w.Dialer == nil {
|
||||
dialer = DefaultDialer
|
||||
}
|
||||
|
||||
// Pass headers from the incoming request to the dialer to forward them to
|
||||
// the final destinations.
|
||||
requestHeader := http.Header{}
|
||||
if origin := req.Header.Get("Origin"); origin != "" {
|
||||
requestHeader.Add("Origin", origin)
|
||||
}
|
||||
for _, prot := range req.Header[http.CanonicalHeaderKey("Sec-WebSocket-Protocol")] {
|
||||
requestHeader.Add("Sec-WebSocket-Protocol", prot)
|
||||
}
|
||||
for _, cookie := range req.Header[http.CanonicalHeaderKey("Cookie")] {
|
||||
requestHeader.Add("Cookie", cookie)
|
||||
}
|
||||
if req.Host != "" {
|
||||
requestHeader.Set("Host", req.Host)
|
||||
}
|
||||
|
||||
// Pass X-Forwarded-For headers too, code below is a part of
|
||||
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
|
||||
// for more information
|
||||
// TODO: use RFC7239 http://tools.ietf.org/html/rfc7239
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
if prior, ok := req.Header["X-Forwarded-For"]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
requestHeader.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
|
||||
// Set the originating protocol of the incoming HTTP request. The SSL might
|
||||
// be terminated on our site and because we doing proxy adding this would
|
||||
// be helpful for applications on the backend.
|
||||
requestHeader.Set("X-Forwarded-Proto", "http")
|
||||
if req.TLS != nil {
|
||||
requestHeader.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
|
||||
// Enable the director to copy any additional headers it desires for
|
||||
// forwarding to the remote server.
|
||||
if w.Director != nil {
|
||||
w.Director(req, requestHeader)
|
||||
}
|
||||
|
||||
// Connect to the backend URL, also pass the headers we get from the requst
|
||||
// together with the Forwarded headers we prepared above.
|
||||
// TODO: support multiplexing on the same backend connection instead of
|
||||
// opening a new TCP connection time for each request. This should be
|
||||
// optional:
|
||||
// http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01
|
||||
connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader)
|
||||
if err != nil {
|
||||
log.Printf("websocketproxy: couldn't dial to remote backend url %s", err)
|
||||
if resp != nil {
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned
|
||||
// along with a non-nil *http.Response so that callers can handle
|
||||
// redirects, authentication, etcetera.
|
||||
if err := copyResponse(rw, resp); err != nil {
|
||||
log.Printf("websocketproxy: couldn't write response after failed remote backend handshake: %s", err)
|
||||
}
|
||||
} else {
|
||||
http.Error(rw, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer connBackend.Close()
|
||||
|
||||
upgrader := w.Upgrader
|
||||
if w.Upgrader == nil {
|
||||
upgrader = DefaultUpgrader
|
||||
}
|
||||
|
||||
// Only pass those headers to the upgrader.
|
||||
upgradeHeader := http.Header{}
|
||||
if hdr := resp.Header.Get("Sec-Websocket-Protocol"); hdr != "" {
|
||||
upgradeHeader.Set("Sec-Websocket-Protocol", hdr)
|
||||
}
|
||||
if hdr := resp.Header.Get("Set-Cookie"); hdr != "" {
|
||||
upgradeHeader.Set("Set-Cookie", hdr)
|
||||
}
|
||||
|
||||
// Now upgrade the existing incoming request to a WebSocket connection.
|
||||
// Also pass the header that we gathered from the Dial handshake.
|
||||
connPub, err := upgrader.Upgrade(rw, req, upgradeHeader)
|
||||
if err != nil {
|
||||
log.Printf("websocketproxy: couldn't upgrade %s", err)
|
||||
return
|
||||
}
|
||||
defer connPub.Close()
|
||||
|
||||
errClient := make(chan error, 1)
|
||||
errBackend := make(chan error, 1)
|
||||
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
|
||||
for {
|
||||
msgType, msg, err := src.ReadMessage()
|
||||
if err != nil {
|
||||
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
|
||||
if e, ok := err.(*websocket.CloseError); ok {
|
||||
if e.Code != websocket.CloseNoStatusReceived {
|
||||
m = websocket.FormatCloseMessage(e.Code, e.Text)
|
||||
}
|
||||
}
|
||||
errc <- err
|
||||
dst.WriteMessage(websocket.CloseMessage, m)
|
||||
break
|
||||
}
|
||||
err = dst.WriteMessage(msgType, msg)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go replicateWebsocketConn(connPub, connBackend, errClient)
|
||||
go replicateWebsocketConn(connBackend, connPub, errBackend)
|
||||
|
||||
var message string
|
||||
select {
|
||||
case err = <-errClient:
|
||||
message = "websocketproxy: Error when copying from backend to client: %v"
|
||||
case err = <-errBackend:
|
||||
message = "websocketproxy: Error when copying from client to backend: %v"
|
||||
|
||||
}
|
||||
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {
|
||||
log.Printf(message, err)
|
||||
}
|
||||
}
|
||||
|
||||
func copyHeader(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func copyResponse(rw http.ResponseWriter, resp *http.Response) error {
|
||||
copyHeader(rw.Header(), resp.Header)
|
||||
rw.WriteHeader(resp.StatusCode)
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err := io.Copy(rw, resp.Body)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user