mirror of
https://github.com/amitbet/vncproxy.git
synced 2025-04-27 02:40:49 +00:00
some more house cleaning, updated license & docs
This commit is contained in:
parent
fa61227d89
commit
01fd227088
27
LICENSE
27
LICENSE
@ -19,3 +19,30 @@ 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.
|
||||
|
||||
|
||||
Based on code collected from various go-vnc implementations:
|
||||
------------------------------------------------------------
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013 Mitchell Hashimoto
|
||||
Copyright (c) 2016-2017 Kate Ward
|
||||
Copyright (c) 2017 Vasiliy Tolstov
|
||||
|
||||
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.
|
24
README.md
24
README.md
@ -1,7 +1,23 @@
|
||||
# VncProxy
|
||||
A RFB proxy, written in go that can save and replay RBS files
|
||||
An RFB proxy, written in go that can save and replay FBS files
|
||||
* supports all modern encodings
|
||||
* supports regular and sockified (noVnc) server connnections
|
||||
* produces FBS files compatible with tightvnc player
|
||||
* can also be used as:
|
||||
* a screen recorder vnc-client
|
||||
* a replay server to show fbs recordings to connecting clients
|
||||
|
||||
Still a work in progress, but the client side already includes all the required encodings
|
||||
understanding the RemoteFrameBuffer protocol is important since we need to save the frameResponses with timestamps
|
||||
This is still a work in progress, and requires some error handling and general tidying up,
|
||||
but the code is already working (see server_test, proxy_test & player_test)
|
||||
- tested on tight encoding with: tightvnc (client+server), noVnc(web client), chickenOfTheVnc(client), vineVnc(server), tigerVnc(client)
|
||||
|
||||
some server code will be added shortly, with the proxying logics.
|
||||
## **Architecture**
|
||||
**Proxy**
|
||||
|
||||

|
||||
|
||||
**Player**
|
||||
|
||||

|
||||
|
||||
The code is based on several implementations of go-vnc including the original one by Mitchell Hashimoto, and the very active fork which belongs to Vasiliy Tolstov.
|
35
architecture/plantUml.wsd
Normal file
35
architecture/plantUml.wsd
Normal file
@ -0,0 +1,35 @@
|
||||
@startuml
|
||||
Title Vncproxy Architecture
|
||||
State VncProxy{
|
||||
State ClientPart
|
||||
State ServerPart
|
||||
state FbsRecorder
|
||||
}
|
||||
State VncClient
|
||||
State VncServer
|
||||
|
||||
VncClient -right-> ServerPart
|
||||
ServerPart -left-> VncClient
|
||||
VncServer -left-> ClientPart
|
||||
ClientPart -right-> VncServer
|
||||
ClientPart -left-> ServerPart
|
||||
ServerPart -right-> ClientPart
|
||||
|
||||
ClientPart -down-> FbsRecorder
|
||||
ServerPart -down-> FbsRecorder
|
||||
@enduml
|
||||
|
||||
|
||||
@startuml
|
||||
Title Vncproxy Fbs Player
|
||||
State VncProxy{
|
||||
|
||||
State ServerPart
|
||||
state FbsPlayer
|
||||
}
|
||||
State VncClient
|
||||
VncClient -left-> ServerPart
|
||||
ServerPart -right-> VncClient
|
||||
|
||||
FbsPlayer -up-> ServerPart
|
||||
@enduml
|
BIN
architecture/player-arch.png
Normal file
BIN
architecture/player-arch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.9 KiB |
BIN
architecture/proxy-arch.png
Normal file
BIN
architecture/proxy-arch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -1,16 +0,0 @@
|
||||
# VNC Library for Go
|
||||
|
||||
go-vnc is a VNC library for Go, initially supporting VNC clients but
|
||||
with the goal of eventually implementing a VNC server.
|
||||
|
||||
This library implements [RFC 6143](http://tools.ietf.org/html/rfc6143).
|
||||
|
||||
## Usage & Installation
|
||||
|
||||
The library is installable via standard `go get`. The package name is `vnc`.
|
||||
|
||||
```
|
||||
$ go get github.com/mitchellh/go-vnc
|
||||
```
|
||||
|
||||
Documentation is available on GoDoc: http://godoc.org/github.com/mitchellh/go-vnc
|
@ -68,13 +68,6 @@ type ClientConfig struct {
|
||||
// disconnected when a connection is established to the VNC server.
|
||||
Exclusive bool
|
||||
|
||||
// The channel that all messages received from the server will be
|
||||
// sent on. If the channel blocks, then the goroutine reading data
|
||||
// from the VNC server may block indefinitely. It is up to the user
|
||||
// of the library to ensure that this channel is properly read.
|
||||
// If this is not set, then all messages will be discarded.
|
||||
ServerMessageCh chan<- common.ServerMessage
|
||||
|
||||
// A slice of supported messages that can be read from the server.
|
||||
// This only needs to contain NEW server messages, and doesn't
|
||||
// need to explicitly contain the RFC-required messages.
|
||||
@ -523,12 +516,6 @@ func (c *ClientConn) mainLoop() {
|
||||
break
|
||||
}
|
||||
logger.Debugf("ClientConn.MainLoop: read & parsed ServerMessage:%d, %s", parsedMsg.Type(), parsedMsg)
|
||||
|
||||
if c.config.ServerMessageCh == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c.config.ServerMessageCh <- parsedMsg
|
||||
}
|
||||
}
|
||||
|
||||
|
14
main.go
14
main.go
@ -21,16 +21,15 @@ func main() {
|
||||
var noauth client.ClientAuthNone
|
||||
authArr := []client.ClientAuth{&client.PasswordAuth{Password: "Ch_#!T@8"}, &noauth}
|
||||
|
||||
vncSrvMessagesChan := make(chan common.ServerMessage)
|
||||
//vncSrvMessagesChan := make(chan common.ServerMessage)
|
||||
|
||||
//rec := listeners.NewRecorder("c:/Users/betzalel/recording.rbs")
|
||||
rec := listeners.NewRecorder("/Users/amitbet/vncRec/recording.rbs")
|
||||
|
||||
clientConn, err := client.NewClientConn(nc,
|
||||
&client.ClientConfig{
|
||||
Auth: authArr,
|
||||
ServerMessageCh: vncSrvMessagesChan,
|
||||
Exclusive: true,
|
||||
Auth: authArr,
|
||||
Exclusive: true,
|
||||
})
|
||||
|
||||
clientConn.Listeners.AddListener(rec)
|
||||
@ -91,8 +90,11 @@ func main() {
|
||||
}()
|
||||
|
||||
//go func() {
|
||||
for msg := range vncSrvMessagesChan {
|
||||
logger.Debugf("message type: %d, content: %v\n", msg.Type(), msg)
|
||||
// for msg := range vncSrvMessagesChan {
|
||||
// logger.Debugf("message type: %d, content: %v\n", msg.Type(), msg)
|
||||
// }
|
||||
for {
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
//}()
|
||||
|
||||
|
@ -2,7 +2,6 @@ package player
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
"vncproxy/client"
|
||||
@ -104,20 +103,17 @@ func loadFbsFile(filename string, conn *server.ServerConn) (*FbsReader, error) {
|
||||
func TestServer(t *testing.T) {
|
||||
|
||||
//chServer := make(chan common.ClientMessage)
|
||||
chClient := make(chan common.ServerMessage)
|
||||
//chClient := make(chan common.ServerMessage)
|
||||
|
||||
cfg := &server.ServerConfig{
|
||||
//SecurityHandlers: []SecurityHandler{&ServerAuthNone{}, &ServerAuthVNC{}},
|
||||
SecurityHandlers: []server.SecurityHandler{&server.ServerAuthNone{}},
|
||||
Encodings: []common.Encoding{&encodings.RawEncoding{}, &encodings.TightEncoding{}, &encodings.CopyRectEncoding{}},
|
||||
PixelFormat: common.NewPixelFormat(32),
|
||||
//ClientMessageCh: chServer,
|
||||
//ServerMessageCh: chClient,
|
||||
ClientMessages: server.DefaultClientMessages,
|
||||
DesktopName: []byte("workDesk"),
|
||||
Height: uint16(768),
|
||||
Width: uint16(1024),
|
||||
//NewConnHandler: serverNewConnHandler,
|
||||
ClientMessages: server.DefaultClientMessages,
|
||||
DesktopName: []byte("workDesk"),
|
||||
Height: uint16(768),
|
||||
Width: uint16(1024),
|
||||
}
|
||||
|
||||
cfg.NewConnHandler = func(cfg *server.ServerConfig, conn *server.ServerConn) error {
|
||||
@ -134,15 +130,8 @@ func TestServer(t *testing.T) {
|
||||
go server.WsServe(url, cfg)
|
||||
go server.TcpServe(":5904", cfg)
|
||||
|
||||
// Process messages coming in on the ClientMessage channel.
|
||||
|
||||
for {
|
||||
msg := <-chClient
|
||||
switch msg.Type() {
|
||||
default:
|
||||
log.Printf("Received message type:%v msg:%v\n", msg.Type(), msg)
|
||||
|
||||
}
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -36,12 +36,11 @@ func (vp *VncProxy) createClientConnection(targetServerUrl string) (*client.Clie
|
||||
var noauth client.ClientAuthNone
|
||||
authArr := []client.ClientAuth{&client.PasswordAuth{Password: vp.targetServersPassword}, &noauth}
|
||||
|
||||
vncSrvMessagesChan := make(chan common.ServerMessage)
|
||||
//vncSrvMessagesChan := make(chan common.ServerMessage)
|
||||
|
||||
clientConn, err := client.NewClientConn(nc,
|
||||
&client.ClientConfig{
|
||||
Auth: authArr,
|
||||
ServerMessageCh: vncSrvMessagesChan,
|
||||
Exclusive: true,
|
||||
})
|
||||
//clientConn.Listener = split
|
||||
@ -145,7 +144,6 @@ func (vp *VncProxy) StartListening() {
|
||||
SecurityHandlers: secHandlers,
|
||||
Encodings: []common.Encoding{&encodings.RawEncoding{}, &encodings.TightEncoding{}, &encodings.CopyRectEncoding{}},
|
||||
PixelFormat: common.NewPixelFormat(32),
|
||||
//ServerMessageCh: chClient,
|
||||
ClientMessages: server.DefaultClientMessages,
|
||||
DesktopName: []byte("workDesk"),
|
||||
Height: uint16(768),
|
||||
|
@ -12,8 +12,7 @@ import (
|
||||
type ServerConn struct {
|
||||
c io.ReadWriter
|
||||
cfg *ServerConfig
|
||||
//br *bufio.Reader
|
||||
//bw *bufio.Writer
|
||||
|
||||
protocol string
|
||||
m sync.Mutex
|
||||
// If the pixel format uses a color map, then this is the color
|
||||
@ -96,25 +95,10 @@ func (c *ServerConn) SetProtoVersion(pv string) {
|
||||
c.protocol = pv
|
||||
}
|
||||
|
||||
// func (c *ServerConn) Flush() error {
|
||||
// // c.m.Lock()
|
||||
// // defer c.m.Unlock()
|
||||
// return c.bw.Flush()
|
||||
// }
|
||||
|
||||
func (c *ServerConn) Close() error {
|
||||
return c.c.(io.ReadWriteCloser).Close()
|
||||
}
|
||||
|
||||
/*
|
||||
func (c *ServerConn) Input() chan *ServerMessage {
|
||||
return c.cfg.ServerMessageCh
|
||||
}
|
||||
|
||||
func (c *ServerConn) Output() chan *ClientMessage {
|
||||
return c.cfg.ClientMessageCh
|
||||
}
|
||||
*/
|
||||
func (c *ServerConn) Read(buf []byte) (int, error) {
|
||||
return c.c.Read(buf)
|
||||
}
|
||||
@ -157,8 +141,6 @@ func (c *ServerConn) Height() uint16 {
|
||||
func (c *ServerConn) Protocol() string {
|
||||
return c.protocol
|
||||
}
|
||||
|
||||
// TODO send desktopsize pseudo encoding
|
||||
func (c *ServerConn) SetWidth(w uint16) {
|
||||
c.fbWidth = w
|
||||
}
|
||||
@ -167,42 +149,19 @@ func (c *ServerConn) SetHeight(h uint16) {
|
||||
}
|
||||
|
||||
func (c *ServerConn) handle() error {
|
||||
//var err error
|
||||
//var wg sync.WaitGroup
|
||||
|
||||
//defer c.Close()
|
||||
|
||||
defer func() {
|
||||
c.Listeners.Consume(&common.RfbSegment{
|
||||
SegmentType: common.SegmentConnectionClosed,
|
||||
})
|
||||
}()
|
||||
|
||||
//create a map of all message types
|
||||
clientMessages := make(map[common.ClientMessageType]common.ClientMessage)
|
||||
for _, m := range c.cfg.ClientMessages {
|
||||
clientMessages[m.Type()] = m
|
||||
}
|
||||
//wg.Add(2)
|
||||
|
||||
// server
|
||||
// go func() error {
|
||||
// //defer wg.Done()
|
||||
// for {
|
||||
// select {
|
||||
// case msg := <-c.cfg.ServerMessageCh:
|
||||
// logger.Debugf("%v", msg)
|
||||
// // if err = msg.Write(c); err != nil {
|
||||
// // return err
|
||||
// // }
|
||||
// case <-c.quit:
|
||||
// c.Close()
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
|
||||
// client
|
||||
//go func() error {
|
||||
//defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-c.quit:
|
||||
@ -238,9 +197,8 @@ func (c *ServerConn) handle() error {
|
||||
return err
|
||||
}
|
||||
|
||||
//logger.Debugf("ServerConn.Handle got client message, type=%s", parsedMsg.Type())
|
||||
logger.Debugf("ServerConn.Handle got ClientMessage: %s, %v", parsedMsg.Type(), parsedMsg)
|
||||
//parsedMsg.Type()
|
||||
|
||||
seg := &common.RfbSegment{
|
||||
SegmentType: common.SegmentFullyParsedClientMessage,
|
||||
Message: parsedMsg,
|
||||
@ -250,12 +208,6 @@ func (c *ServerConn) handle() error {
|
||||
logger.Errorf("ServerConn.Handle: listener consume err %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
//c.cfg.ClientMessageCh <- parsedMsg
|
||||
}
|
||||
}
|
||||
//}()
|
||||
|
||||
//wg.Wait()
|
||||
//return nil
|
||||
}
|
||||
|
@ -17,62 +17,32 @@ var DefaultClientMessages = []common.ClientMessage{
|
||||
&ClientCutText{},
|
||||
}
|
||||
|
||||
//var _ ServerConn = (*ServerConn)(nil)
|
||||
|
||||
// ServerMessage represents a Client-to-Server RFB message type.
|
||||
// type ServerMessageType uint8
|
||||
|
||||
// //go:generate stringer -type=ServerMessageType
|
||||
|
||||
// // Client-to-Server message types.
|
||||
// const (
|
||||
// FramebufferUpdateMsgType ServerMessageType = iota
|
||||
// SetColorMapEntriesMsgType
|
||||
// BellMsgType
|
||||
// ServerCutTextMsgType
|
||||
// )
|
||||
|
||||
// FramebufferUpdate holds a FramebufferUpdate wire format message.
|
||||
type FramebufferUpdate struct {
|
||||
_ [1]byte // pad
|
||||
_ [1]byte // padding
|
||||
NumRect uint16 // number-of-rectangles
|
||||
Rects []*common.Rectangle // rectangles
|
||||
}
|
||||
|
||||
// func (*FramebufferUpdate) Type() ServerMessageType {
|
||||
// return FramebufferUpdateMsgType
|
||||
// }
|
||||
|
||||
type ServerHandler func(*ServerConfig, *ServerConn) error
|
||||
|
||||
type ServerConfig struct {
|
||||
//VersionHandler ServerHandler
|
||||
//SecurityHandler ServerHandler
|
||||
SecurityHandlers []SecurityHandler
|
||||
//ClientInitHandler ServerHandler
|
||||
//ServerInitHandler ServerHandler
|
||||
Encodings []common.Encoding
|
||||
PixelFormat *common.PixelFormat
|
||||
ColorMap *common.ColorMap
|
||||
//ClientMessageCh chan common.ClientMessage
|
||||
//ServerMessageCh chan common.ServerMessage
|
||||
ClientMessages []common.ClientMessage
|
||||
DesktopName []byte
|
||||
Height uint16
|
||||
Width uint16
|
||||
UseDummySession bool
|
||||
Encodings []common.Encoding
|
||||
PixelFormat *common.PixelFormat
|
||||
ColorMap *common.ColorMap
|
||||
ClientMessages []common.ClientMessage
|
||||
DesktopName []byte
|
||||
Height uint16
|
||||
Width uint16
|
||||
UseDummySession bool
|
||||
|
||||
//handler to allow for registering for messages, this can't be a channel
|
||||
//because of the websockets handler function which will kill the connection on exit if conn.handle() is run on another thread
|
||||
NewConnHandler ServerHandler
|
||||
}
|
||||
|
||||
func wsHandlerFunc(ws io.ReadWriter, cfg *ServerConfig, sessionId string) {
|
||||
// header := ws.Request().Header
|
||||
// url := ws.Request().URL
|
||||
// //stam := header.Get("Origin")
|
||||
// logger.Debugf("header: %v\nurl: %v", header, url)
|
||||
// io.Copy(ws, ws)
|
||||
|
||||
err := attachNewServerConn(ws, cfg, sessionId)
|
||||
if err != nil {
|
||||
log.Fatalf("Error attaching new connection. %v", err)
|
||||
@ -80,7 +50,6 @@ func wsHandlerFunc(ws io.ReadWriter, cfg *ServerConfig, sessionId string) {
|
||||
}
|
||||
|
||||
func WsServe(url string, cfg *ServerConfig) error {
|
||||
//server := WsServer1{cfg}
|
||||
server := WsServer{cfg}
|
||||
server.Listen(url, WsHandler(wsHandlerFunc))
|
||||
return nil
|
||||
@ -97,9 +66,6 @@ func TcpServe(url string, cfg *ServerConfig) error {
|
||||
return err
|
||||
}
|
||||
go attachNewServerConn(c, cfg, "dummySession")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -22,8 +22,6 @@ func TestServer(t *testing.T) {
|
||||
SecurityHandlers: []SecurityHandler{&ServerAuthVNC{"Ch_#!T@8"}},
|
||||
Encodings: []common.Encoding{&encodings.RawEncoding{}, &encodings.TightEncoding{}, &encodings.CopyRectEncoding{}},
|
||||
PixelFormat: common.NewPixelFormat(32),
|
||||
//ClientMessageCh: chServer,
|
||||
ServerMessageCh: chClient,
|
||||
ClientMessages: DefaultClientMessages,
|
||||
DesktopName: []byte("workDesk"),
|
||||
Height: uint16(768),
|
||||
|
@ -1,104 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"vncproxy/logger"
|
||||
|
||||
"bytes"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WsServer1 struct {
|
||||
cfg *ServerConfig
|
||||
}
|
||||
|
||||
type WsHandler1 func(io.ReadWriter, *ServerConfig)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
}}
|
||||
|
||||
type WsConnection struct {
|
||||
Reader WsReader
|
||||
Writer WsWriter
|
||||
}
|
||||
|
||||
func NewWsConnection(c *websocket.Conn) *WsConnection {
|
||||
return &WsConnection{
|
||||
WsReader{},
|
||||
WsWriter{c},
|
||||
}
|
||||
}
|
||||
|
||||
type WsWriter struct {
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
type WsReader struct {
|
||||
Buff bytes.Buffer
|
||||
}
|
||||
|
||||
func (wr WsReader) Read(p []byte) (n int, err error) {
|
||||
return wr.Buff.Read(p)
|
||||
}
|
||||
|
||||
func (wr WsWriter) Write(p []byte) (int, error) {
|
||||
err := wr.conn.WriteMessage(websocket.BinaryMessage, p)
|
||||
return len(p), err
|
||||
}
|
||||
|
||||
func handleConnection(w http.ResponseWriter, r *http.Request) {
|
||||
log.Print("got connection:", r.URL)
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
|
||||
if err != nil {
|
||||
log.Print("upgrade:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
myConn := NewWsConnection(c)
|
||||
|
||||
for {
|
||||
mt, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("read:", err)
|
||||
break
|
||||
}
|
||||
if mt == websocket.BinaryMessage {
|
||||
myConn.Reader.Buff.Write(message)
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
// err = c.WriteMessage(mt, message)
|
||||
// if err != nil {
|
||||
// log.Println("write:", err)
|
||||
// break
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// This example demonstrates a trivial echo server.
|
||||
func (wsServer *WsServer1) Listen(urlStr string, handlerFunc WsHandler) {
|
||||
//http.Handle("/", websocket.Handler(EchoHandler))
|
||||
if urlStr == "" {
|
||||
urlStr = "/"
|
||||
}
|
||||
url, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
logger.Errorf("error while parsing url: ", err)
|
||||
}
|
||||
|
||||
http.HandleFunc(url.Path, handleConnection)
|
||||
|
||||
err = http.ListenAndServe(url.Host, nil)
|
||||
if err != nil {
|
||||
panic("ListenAndServe: " + err.Error())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user