├── Makefile ├── client.go ├── client_test.go ├── config.go ├── editsocket.go ├── protocol.go ├── protocol_test.go ├── release └── get.sh ├── rpcmultiplex.go ├── server.go └── zedrem.go /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | SHELL=/bin/bash 4 | 5 | build: deps 6 | go build 7 | 8 | release: deps golang-crosscompile 9 | source golang-crosscompile/crosscompile.bash; \ 10 | go-darwin-386 build -o release/zedrem-Darwin-i386; \ 11 | go-darwin-amd64 build -o release/zedrem-Darwin-x86_64; \ 12 | go-linux-386 build -o release/zedrem-Linux-i386; \ 13 | go-linux-386 build -o release/zedrem-Linux-i686; \ 14 | go-linux-amd64 build -o release/zedrem-Linux-x86_64; \ 15 | go-linux-arm build -o release/zedrem-Linux-armv6l; \ 16 | go-linux-arm build -o release/zedrem-Linux-armv7l; \ 17 | go-freebsd-386 build -o release/zedrem-FreeBSD-i386; \ 18 | go-freebsd-amd64 build -o release/zedrem-FreeBSD-amd64; \ 19 | go-windows-386 build -o release/zedrem.exe 20 | 21 | golang-crosscompile: 22 | git clone https://github.com/davecheney/golang-crosscompile.git 23 | 24 | deps: 25 | go get golang.org/x/net/websocket 26 | go get github.com/pborman/uuid 27 | go get gopkg.in/gcfg.v1 28 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "golang.org/x/net/websocket" 6 | "crypto/tls" 7 | "encoding/json" 8 | "github.com/pborman/uuid" 9 | "flag" 10 | "fmt" 11 | "io" 12 | "io/ioutil" 13 | "log" 14 | "mime" 15 | "net/http" 16 | "net/url" 17 | "os" 18 | "os/signal" 19 | "syscall" 20 | "path/filepath" 21 | "strings" 22 | "time" 23 | ) 24 | 25 | type HttpError interface { 26 | error 27 | StatusCode() int 28 | } 29 | 30 | // Errors 31 | type HandlingError struct { 32 | message string 33 | } 34 | 35 | func (self *HandlingError) StatusCode() int { 36 | return 500 37 | } 38 | 39 | func (self *HandlingError) Error() string { 40 | return self.message 41 | } 42 | 43 | func NewHandlingError(message string) HttpError { 44 | return &HandlingError{message} 45 | } 46 | 47 | type httpError struct { 48 | statusCode int 49 | message string 50 | } 51 | 52 | func (self *httpError) Error() string { 53 | return self.message 54 | } 55 | 56 | func (self *httpError) StatusCode() int { 57 | return self.statusCode 58 | } 59 | 60 | func NewHttpError(statusCode int, message string) HttpError { 61 | return &httpError{statusCode, message} 62 | } 63 | 64 | func safePath(rootPath string, path string) (string, error) { 65 | absPath, err := filepath.Abs(filepath.Join(rootPath, path)) 66 | if err != nil { 67 | return "", NewHttpError(500, err.Error()) 68 | } 69 | if !strings.HasPrefix(absPath, rootPath) { 70 | return "", NewHandlingError("Hacking attempt") 71 | } 72 | return absPath, nil 73 | } 74 | 75 | var writeLock = make(map[string]chan bool) 76 | 77 | type RootedRPCHandler struct { 78 | rootPath string 79 | } 80 | 81 | func (self *RootedRPCHandler) handleRequest(requestChannel chan []byte, responseChannel chan []byte, closeChannel chan bool) { 82 | commandBuffer, ok := <-requestChannel 83 | if !ok { 84 | return 85 | } 86 | command := string(commandBuffer) 87 | // headers 88 | _, ok = <-requestChannel 89 | if !ok { 90 | return 91 | } 92 | 93 | var err HttpError 94 | commandParts := strings.Split(command, " ") 95 | method := commandParts[0] 96 | path := strings.Join(commandParts[1:], "/") 97 | if strings.HasPrefix(path, "/") { 98 | path = path[1:] 99 | } 100 | switch method { 101 | case "GET": 102 | err = self.handleGet(path, requestChannel, responseChannel) 103 | case "HEAD": 104 | err = self.handleHead(path, requestChannel, responseChannel) 105 | case "PUT": 106 | err = self.handlePut(path, requestChannel, responseChannel) 107 | case "DELETE": 108 | err = self.handleDelete(path, requestChannel, responseChannel) 109 | case "POST": 110 | err = self.handlePost(path, requestChannel, responseChannel) 111 | } 112 | if err != nil { 113 | sendError(responseChannel, err, commandParts[0] != "HEAD") 114 | } 115 | responseChannel <- DELIMITERBUFFER 116 | closeChannel <- true 117 | } 118 | 119 | func sendError(responseChannel chan []byte, err HttpError, withMessageInBody bool) { 120 | responseChannel <- statusCodeBuffer(err.StatusCode()) 121 | 122 | if withMessageInBody { 123 | responseChannel <- headerBuffer(map[string]string{"Content-Type": "text/plain"}) 124 | responseChannel <- []byte(err.Error()) 125 | } else { 126 | responseChannel <- headerBuffer(map[string]string{"Content-Length": "0"}) 127 | } 128 | } 129 | 130 | func dropUntilDelimiter(requestChannel chan []byte) { 131 | for { 132 | buffer, ok := <-requestChannel 133 | if !ok { 134 | break 135 | } 136 | if IsDelimiter(buffer) { 137 | break 138 | } 139 | } 140 | } 141 | 142 | func headerBuffer(headers map[string]string) []byte { 143 | var headerBuffer bytes.Buffer 144 | for h, v := range headers { 145 | headerBuffer.Write([]byte(fmt.Sprintf("%s: %s\n", h, v))) 146 | } 147 | bytes := headerBuffer.Bytes() 148 | return bytes[:len(bytes)-1] 149 | } 150 | 151 | func statusCodeBuffer(code int) []byte { 152 | return IntToBytes(code) 153 | } 154 | 155 | func waitForLock(path string) { 156 | if writeLock[path] != nil { 157 | <-writeLock[path] 158 | } 159 | } 160 | 161 | func (self *RootedRPCHandler) handleGet(path string, requestChannel chan []byte, responseChannel chan []byte) HttpError { 162 | waitForLock(path) 163 | 164 | dropUntilDelimiter(requestChannel) 165 | safePath, err := safePath(self.rootPath, path) 166 | if err != nil { 167 | return err.(HttpError) 168 | } 169 | stat, err := os.Stat(safePath) 170 | if err != nil { 171 | return NewHttpError(404, "Not found") 172 | } 173 | responseChannel <- statusCodeBuffer(200) 174 | if stat.IsDir() { 175 | responseChannel <- headerBuffer(map[string]string{"Content-Type": "text/plain"}) 176 | files, _ := ioutil.ReadDir(safePath) 177 | for _, f := range files { 178 | if f.Name()[0] == '.' { 179 | continue 180 | } 181 | if f.IsDir() { 182 | responseChannel <- []byte(fmt.Sprintf("%s/\n", f.Name())) 183 | } else { 184 | responseChannel <- []byte(fmt.Sprintf("%s\n", f.Name())) 185 | } 186 | } 187 | } else { // File 188 | mimeType := mime.TypeByExtension(filepath.Ext(safePath)) 189 | if mimeType == "" { 190 | mimeType = "application/octet-stream" 191 | } 192 | responseChannel <- headerBuffer(map[string]string{ 193 | "Content-Type": mimeType, 194 | "ETag": stat.ModTime().String(), 195 | }) 196 | f, err := os.Open(safePath) 197 | if err != nil { 198 | return NewHttpError(500, "Could not open file") 199 | } 200 | defer f.Close() 201 | for { 202 | buffer := make([]byte, BUFFER_SIZE) 203 | n, _ := f.Read(buffer) 204 | if n == 0 { 205 | break 206 | } 207 | responseChannel <- buffer[:n] 208 | } 209 | } 210 | return nil 211 | } 212 | 213 | func (self *RootedRPCHandler) handleHead(path string, requestChannel chan []byte, responseChannel chan []byte) HttpError { 214 | waitForLock(path) 215 | 216 | safePath, err := safePath(self.rootPath, path) 217 | dropUntilDelimiter(requestChannel) 218 | if err != nil { 219 | return err.(HttpError) 220 | } 221 | stat, err := os.Stat(safePath) 222 | if err != nil { 223 | return NewHttpError(404, "Not found") 224 | } 225 | responseChannel <- statusCodeBuffer(200) 226 | fileType := "file" 227 | if stat.IsDir() { 228 | fileType = "directory" 229 | } 230 | responseChannel <- headerBuffer(map[string]string{ 231 | "ETag": stat.ModTime().String(), 232 | "Content-Length": "0", 233 | "X-Type": fileType, 234 | }) 235 | return nil 236 | } 237 | 238 | func (self *RootedRPCHandler) handlePut(path string, requestChannel chan []byte, responseChannel chan []byte) HttpError { 239 | if writeLock[path] != nil { 240 | // Already writing 241 | dropUntilDelimiter(requestChannel) 242 | return NewHttpError(500, "Write already going on") 243 | } 244 | 245 | writeLock[path] = make(chan bool) 246 | 247 | defer func() { 248 | close(writeLock[path]) 249 | writeLock[path] = nil 250 | }() 251 | 252 | safePath, err := safePath(self.rootPath, path) 253 | if err != nil { 254 | dropUntilDelimiter(requestChannel) 255 | return err.(HttpError) 256 | } 257 | dir := filepath.Dir(safePath) 258 | os.MkdirAll(dir, 0777) 259 | 260 | // To avoid corrupted files, we'll write to a temp path first 261 | tempPath := dir + "/.zedtmp." + uuid.New() 262 | f, err := os.Create(tempPath) 263 | if err != nil { 264 | dropUntilDelimiter(requestChannel) 265 | return NewHttpError(500, fmt.Sprintf("Could not create file: %s", tempPath)) 266 | } 267 | for { 268 | buffer := <-requestChannel 269 | if IsDelimiter(buffer) { 270 | break 271 | } 272 | _, err := f.Write(buffer) 273 | if err != nil { 274 | dropUntilDelimiter(requestChannel) 275 | return NewHttpError(500, "Could not write to file") 276 | } 277 | } 278 | f.Sync() 279 | f.Close() 280 | 281 | // Get existing file permissions 282 | var mode os.FileMode = 0666 283 | stat, err := os.Stat(safePath) 284 | if err == nil { 285 | mode = stat.Mode() 286 | } 287 | // And then copy it over again 288 | 289 | f, err = os.Open(tempPath) 290 | if err != nil { 291 | return NewHttpError(500, fmt.Sprintf("Could not read temporary file for copy: %s", tempPath)) 292 | } 293 | fout, err := os.OpenFile(safePath, os.O_WRONLY | os.O_TRUNC | os.O_CREATE, mode) 294 | if err != nil { 295 | return NewHttpError(500, fmt.Sprintf("Could not open target file for copy: %s", safePath)) 296 | } 297 | 298 | io.Copy(fout, f) 299 | f.Close() 300 | fout.Close() 301 | 302 | os.Remove(tempPath) 303 | 304 | // if err := os.Chmod(tempPath, mode); err != nil { 305 | // return NewHttpError(500, "unable to chmod tmpfile: " + err.Error()) 306 | // } 307 | 308 | // // Rename the temp file to a the real file. This is done "atomically", 309 | // // so that even if something goes weird, we'll either have an old or new version. 310 | // if err := os.Rename(tempPath, safePath); err != nil { 311 | // return NewHttpError(500, "Unable to replace old version: " + err.Error()) 312 | // } 313 | 314 | stat, _ = os.Stat(safePath) 315 | responseChannel <- statusCodeBuffer(200) 316 | responseChannel <- headerBuffer(map[string]string{ 317 | "Content-Type": "text/plain", 318 | "ETag": stat.ModTime().String(), 319 | }) 320 | responseChannel <- []byte("OK") 321 | return nil 322 | } 323 | 324 | func (self *RootedRPCHandler) handleDelete(path string, requestChannel chan []byte, responseChannel chan []byte) HttpError { 325 | waitForLock(path) 326 | 327 | safePath, err := safePath(self.rootPath, path) 328 | if err != nil { 329 | dropUntilDelimiter(requestChannel) 330 | return err.(HttpError) 331 | } 332 | _, err = os.Stat(safePath) 333 | if err != nil { 334 | return NewHttpError(404, "Not found") 335 | } 336 | err = os.Remove(safePath) 337 | if err != nil { 338 | return NewHttpError(500, "Could not delete") 339 | } 340 | responseChannel <- statusCodeBuffer(200) 341 | responseChannel <- headerBuffer(map[string]string{ 342 | "Content-Type": "text/plain", 343 | }) 344 | responseChannel <- []byte("OK") 345 | 346 | return nil 347 | } 348 | 349 | func walkDirectory(responseChannel chan []byte, root string, path string) { 350 | files, _ := ioutil.ReadDir(filepath.Join(root, path)) 351 | for _, f := range files { 352 | if f.IsDir() { 353 | walkDirectory(responseChannel, root, filepath.Join(path, f.Name())) 354 | } else { 355 | responseChannel <- []byte(fmt.Sprintf("/%s\n", filepath.Join(path, f.Name()))) 356 | } 357 | } 358 | } 359 | 360 | func readWholeBody(requestChannel chan []byte) []byte { 361 | var byteBuffer bytes.Buffer 362 | for { 363 | buffer := <-requestChannel 364 | if IsDelimiter(buffer) { 365 | break 366 | } 367 | byteBuffer.Write(buffer) 368 | } 369 | return byteBuffer.Bytes() 370 | } 371 | 372 | func (self *RootedRPCHandler) handlePost(path string, requestChannel chan []byte, responseChannel chan []byte) HttpError { 373 | safePath, err := safePath(self.rootPath, path) 374 | body := string(readWholeBody(requestChannel)) 375 | if err != nil { 376 | return err.(HttpError) 377 | } 378 | _, err = os.Stat(safePath) 379 | if err != nil { 380 | return NewHttpError(http.StatusNotFound, "Not found") 381 | } 382 | 383 | queryValues, err := url.ParseQuery(body) 384 | if err != nil { 385 | return NewHttpError(http.StatusInternalServerError, "Could not parse body as HTTP post") 386 | } 387 | 388 | action := queryValues["action"][0] 389 | switch action { 390 | case "filelist": 391 | responseChannel <- statusCodeBuffer(200) 392 | responseChannel <- headerBuffer(map[string]string{ 393 | "Content-Type": "text/plain", 394 | }) 395 | walkDirectory(responseChannel, safePath, "") 396 | case "version": 397 | responseChannel <- statusCodeBuffer(200) 398 | responseChannel <- headerBuffer(map[string]string{ 399 | "Content-Type": "text/plain", 400 | }) 401 | responseChannel <- []byte(PROTOCOL_VERSION) 402 | default: 403 | return NewHttpError(http.StatusNotImplemented, "No such action") 404 | } 405 | 406 | return nil 407 | } 408 | 409 | // Side-effect: writes to rootPath 410 | func ParseClientFlags(args []string) (url string, userKey string, rootPath string) { 411 | config := ParseConfig() 412 | 413 | flagSet := flag.NewFlagSet("zedrem", flag.ExitOnError) 414 | var stats bool 415 | flagSet.StringVar(&url, "u", config.Client.Url, "URL to connect to") 416 | flagSet.StringVar(&userKey, "key", config.Client.UserKey, "User key to use") 417 | flagSet.BoolVar(&stats, "stats", false, "Whether to print go-routine count and memory usage stats periodically.") 418 | flagSet.Parse(args) 419 | if stats { 420 | go PrintStats() 421 | } 422 | if flagSet.NArg() == 0 { 423 | rootPath = "." 424 | } else { 425 | rootPath = args[len(args)-1] 426 | } 427 | return 428 | } 429 | 430 | func ListenForSignals() { 431 | sigs := make(chan os.Signal, 1) 432 | signal.Notify(sigs, 433 | syscall.SIGHUP, 434 | syscall.SIGINT, 435 | syscall.SIGTERM, 436 | syscall.SIGQUIT) 437 | go func() { 438 | _ = <-sigs 439 | os.Exit(0) 440 | }() 441 | } 442 | 443 | func RunClient(url string, id string, userKey string, rootPath string) { 444 | rootPath, _ = filepath.Abs(rootPath) 445 | ListenForSignals() 446 | socketUrl := fmt.Sprintf("%s/clientsocket", url) 447 | var ws *websocket.Conn 448 | var timeout time.Duration = 1e8 449 | config, err := websocket.NewConfig(socketUrl, socketUrl) 450 | if err != nil { 451 | fmt.Println(err) 452 | return 453 | } 454 | config.TlsConfig = new(tls.Config) 455 | // Disable this when getting a proper certificate 456 | config.TlsConfig.InsecureSkipVerify = true 457 | for { 458 | time.Sleep(timeout) 459 | var err error 460 | ws, err = websocket.DialConfig(config) 461 | timeout *= 2 462 | if err != nil { 463 | fmt.Println("Could not yet connect:", err.Error(), ", trying again in", timeout) 464 | } else { 465 | break 466 | } 467 | } 468 | 469 | buffer, _ := json.Marshal(HelloMessage{"0.1", id, userKey}) 470 | 471 | if _, err := ws.Write(buffer); err != nil { 472 | log.Fatal(err) 473 | return 474 | } 475 | connectUrl := strings.Replace(url, "ws://", "http://", 1) 476 | connectUrl = strings.Replace(connectUrl, "wss://", "https://", 1) 477 | multiplexer := NewRPCMultiplexer(ws, &RootedRPCHandler{rootPath}) 478 | 479 | if userKey == "" { 480 | fmt.Print("In the Zed application copy and paste following URL to edit:\n\n") 481 | fmt.Printf(" %s/fs/%s\n\n", connectUrl, id) 482 | } else { 483 | fmt.Println("A Zed window should now open. If not, make sure Zed is running and configured with the correct userKey.") 484 | } 485 | fmt.Println("Press Ctrl-c to quit.") 486 | err = multiplexer.Multiplex() 487 | if err != nil { 488 | // TODO do this in a cleaner way (reconnect, that is) 489 | if err.Error() == "no-client" { 490 | fmt.Printf("ERROR: Your Zed editor is not currently connected to zedrem server %s.\nBe sure Zed is running and the project picker is open.\n", url) 491 | } else { 492 | RunClient(url, id, userKey, rootPath) 493 | } 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | type testReadWriter struct { 8 | readChannel chan []byte 9 | writeChannel chan []byte 10 | } 11 | 12 | type testError struct { 13 | message string 14 | } 15 | 16 | func (e *testError) Error() string { 17 | return e.message 18 | } 19 | 20 | func (rw *testReadWriter) Read(p []byte) (n int, err error) { 21 | buffer, ok := <-rw.readChannel 22 | if !ok { 23 | return 0, &testError{"Closed"} 24 | } 25 | for i := range(buffer) { 26 | p[i] = buffer[i] 27 | } 28 | return len(buffer), nil 29 | } 30 | 31 | func (rw *testReadWriter) Write(p []byte) (n int, err error) { 32 | rw.writeChannel <- p 33 | return len(p), nil 34 | } 35 | 36 | func (rw *testReadWriter) Close() { 37 | close(rw.readChannel) 38 | } 39 | 40 | func echoHandler(requestChannel chan []byte, responseChannel chan []byte, closeChannel chan bool) { 41 | buffer := <-requestChannel 42 | buffer2 := <-requestChannel 43 | responseChannel <- bytes.Join([][]byte{buffer, buffer2}, []byte{}) 44 | closeChannel <- true 45 | } 46 | /* 47 | func TestRPC(t *testing.T) { 48 | readWriter := &testReadWriter { 49 | readChannel: make(chan []byte, 2), 50 | writeChannel: make(chan []byte), 51 | } 52 | m := NewRPCMultiplexer(readWriter, echoHandler) 53 | go m.Multiplex() 54 | fmt.Println("Go routines", runtime.NumGoroutine()) 55 | var i byte 56 | for i = 0; i < 5; i++ { 57 | testBuffer := []byte{i, i+1, i+2, i+3} 58 | readWriter.readChannel <- testBuffer 59 | } 60 | for i = 0; i < 5; i++ { 61 | testBuffer := []byte{i, i+4, i+5, i+6} 62 | readWriter.readChannel <- testBuffer 63 | } 64 | for i = 0; i < 5; i++ { 65 | response := <-readWriter.writeChannel 66 | base := response[0] 67 | var j byte 68 | for j = 1; j < byte(len(response)); j++ { 69 | if response[j] != base + j { 70 | t.Fail() 71 | } 72 | } 73 | fmt.Println("Got back", response) 74 | } 75 | fmt.Println("Go routines", runtime.NumGoroutine()) 76 | } 77 | 78 | */ 79 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "gopkg.in/gcfg.v1" 5 | "os" 6 | "fmt" 7 | ) 8 | 9 | type Config struct { 10 | Client struct { 11 | Url string 12 | UserKey string 13 | } 14 | 15 | Server struct { 16 | Ip string 17 | Port int 18 | Sslcert string 19 | Sslkey string 20 | } 21 | } 22 | 23 | func ParseConfig() Config { 24 | var config Config 25 | config.Client.Url = "wss://remote.zedapp.org:443" 26 | config.Server.Ip = "0.0.0.0" 27 | config.Server.Port = 7337 28 | 29 | configFile := os.ExpandEnv("$HOME/.zedremrc") 30 | if _, err := os.Stat(configFile); err == nil { 31 | err = gcfg.ReadFileInto(&config, configFile) 32 | if err != nil { 33 | fmt.Println("Could not read config file ~/.zedremrc", err); 34 | os.Exit(4) 35 | } 36 | } 37 | 38 | return config 39 | } 40 | -------------------------------------------------------------------------------- /editsocket.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "encoding/json" 6 | "errors" 7 | "golang.org/x/net/websocket" 8 | ) 9 | 10 | 11 | var editorClients map[string]*EditorClient = make(map[string]*EditorClient) 12 | 13 | type EditorClient struct { 14 | id string 15 | writeChannels []chan string 16 | } 17 | 18 | func GetEditorClientChannel(uuid string) *EditorClient { 19 | client, ok := editorClients[uuid] 20 | if !ok { 21 | client = &EditorClient { 22 | id: uuid, 23 | writeChannels: make([]chan string, 0), 24 | } 25 | editorClients[uuid] = client 26 | } 27 | return client 28 | } 29 | 30 | func(client *EditorClient) NewChannel() chan string { 31 | ch := make(chan string, 20) 32 | client.writeChannels = append(client.writeChannels, ch) 33 | return ch 34 | } 35 | 36 | func (client *EditorClient) Send(editId string) error { 37 | if len(client.writeChannels) == 0 { 38 | return errors.New("no-client") 39 | } 40 | for _, ch := range client.writeChannels { 41 | ch <- editId 42 | } 43 | return nil 44 | } 45 | 46 | func (client *EditorClient) DisconnectChannel(ch chan string) { 47 | for i, curCh := range client.writeChannels { 48 | if curCh == ch { 49 | client.writeChannels = append(client.writeChannels[:i], client.writeChannels[i+1:]...) 50 | close(ch) 51 | } 52 | } 53 | 54 | if len(client.writeChannels) == 0 { 55 | // Delete client object altogether 56 | delete(editorClients, client.id) 57 | } 58 | } 59 | 60 | var pongBuff []byte = []byte(`{"type": "pong"}`) 61 | 62 | 63 | func editorSocketServer(ws *websocket.Conn) { 64 | defer ws.Close() 65 | buffer := make([]byte, BUFFER_SIZE) 66 | n, err := ws.Read(buffer) 67 | var hello HelloMessage 68 | err = json.Unmarshal(buffer[:n], &hello) 69 | if err != nil { 70 | fmt.Println("Could not parse welcome message.") 71 | return 72 | } 73 | 74 | fmt.Println("Edit client", hello.UUID, "connected") 75 | 76 | client := GetEditorClientChannel(hello.UUID) 77 | clientChan := client.NewChannel() 78 | 79 | closed := false 80 | 81 | closeSocket := func() { 82 | if closed { 83 | return 84 | } 85 | closed = true 86 | fmt.Println("Client disconnected", hello.UUID) 87 | client.DisconnectChannel(clientChan) 88 | } 89 | 90 | defer closeSocket() 91 | 92 | go func() { 93 | for { 94 | buf := make([]byte, 1024) 95 | n, err := ws.Read(buf) 96 | if err != nil { 97 | closeSocket() 98 | return 99 | } 100 | var message EditSocketMessage 101 | err = json.Unmarshal(buf[:n], &message) 102 | if message.MessageType == "ping" { 103 | ws.Write(pongBuff) 104 | } 105 | } 106 | 107 | }() 108 | 109 | for { 110 | url, request_ok := <-clientChan 111 | if !request_ok { 112 | return 113 | } 114 | messageBuf, err := json.Marshal(EditSocketMessage{"open", url}) 115 | if err != nil { 116 | fmt.Println("Couldn't serialize URL") 117 | continue 118 | } 119 | _, err = ws.Write(messageBuf) 120 | if err != nil { 121 | fmt.Println("Got error", err) 122 | return 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /protocol.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "bytes" 6 | ) 7 | 8 | type HelloMessage struct { 9 | Version string 10 | UUID string 11 | UserKey string 12 | } 13 | 14 | type EditSocketMessage struct { 15 | MessageType string `json:"type"` 16 | Url string `json:"url"` 17 | } 18 | 19 | const DELIMITER = "11~~~~~!!END!!~~~~~11" 20 | var DELIMITERBUFFER = []byte(DELIMITER) 21 | const BUFFER_SIZE = 4096 22 | 23 | const PROTOCOL_VERSION = "1.0" 24 | 25 | func ReadFrame(r io.Reader) (requestId byte, buffer []byte, err error) { 26 | buffer = nil 27 | requestIdBuffer := make([]byte, 1) 28 | _, err = io.ReadFull(r, requestIdBuffer) 29 | requestId = requestIdBuffer[0] 30 | lengthBuffer := make([]byte, 2) 31 | if err != nil { 32 | return 33 | } 34 | _, err = io.ReadFull(r, lengthBuffer) 35 | if err != nil { 36 | return 37 | } 38 | length := BytesToInt(lengthBuffer) 39 | buffer = make([]byte, length) 40 | _, err = io.ReadFull(r, buffer) 41 | if err != nil { 42 | return 43 | } 44 | return 45 | } 46 | 47 | func WriteFrame(w io.Writer, requestId byte, buffer []byte) error { 48 | _, err := w.Write([]byte{requestId}) 49 | if err != nil { 50 | return err 51 | } 52 | _, err = w.Write(IntToBytes(len(buffer))) 53 | if err != nil { 54 | return err 55 | } 56 | totalWritten := 0 57 | for totalWritten < len(buffer) { 58 | n, err := w.Write(buffer[totalWritten:]) 59 | if err != nil { 60 | return err 61 | } 62 | totalWritten += n 63 | } 64 | return err 65 | } 66 | 67 | func IsDelimiter(buffer []byte) bool { 68 | if len(buffer) != len(DELIMITER) { 69 | return false 70 | } else { 71 | return bytes.Equal(buffer, DELIMITERBUFFER) 72 | } 73 | return false 74 | } 75 | 76 | func IntToBytes(n int) []byte { 77 | buf := make([]byte, 2) 78 | buf[0] = byte(n / 256) 79 | buf[1] = byte(n % 256) 80 | return buf 81 | } 82 | 83 | func BytesToInt(buf []byte) int { 84 | return int(buf[0]) * 256 + int(buf[1]) 85 | } 86 | -------------------------------------------------------------------------------- /protocol_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "fmt" 6 | "bytes" 7 | ) 8 | 9 | func TestFramer(t *testing.T) { 10 | var byteBuffer bytes.Buffer 11 | for i := 0; i < 20; i++ { 12 | fmt.Println(i) 13 | buf := make([]byte, i * 1024) 14 | for j := 0; j < len(buf); j++ { 15 | buf[j] = byte(j % 256) 16 | } 17 | WriteFrame(&byteBuffer, byte(i), buf) 18 | reqId, readBuf, err := ReadFrame(&byteBuffer) 19 | if err != nil { 20 | t.Fail() 21 | } 22 | if reqId != byte(i) { 23 | t.Fail() 24 | } 25 | if !bytes.Equal(buf, readBuf) { 26 | t.Fail() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /release/get.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OS=`uname -s` 4 | PROC=`uname -m` 5 | 6 | URL="http://get.zedapp.org/zedrem-$OS-$PROC" 7 | 8 | curl -f $URL > zedrem 2> /dev/null 9 | if [ $? == 0 ]; then 10 | chmod +x zedrem 11 | 12 | echo "Done, zedrem downloaded into current directory, to start: ./zedrem" 13 | echo "For help: ./zedrem --help" 14 | else 15 | echo "It appears there is no pre-compiled version of zedrem available for your platform: $OS-$PROC." 16 | echo "I expected it to be located here: $URL" 17 | echo "Please compile your own, see: https://github.com/zedapp/zedrem for instructions" 18 | fi 19 | -------------------------------------------------------------------------------- /rpcmultiplex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "errors" 7 | ) 8 | 9 | type Request struct { 10 | requestChannel chan []byte 11 | responseChannel chan []byte 12 | closeChannel chan bool 13 | } 14 | 15 | type RPCHandler interface { 16 | handleRequest(requestChannel chan []byte, responseChannel chan []byte, closeChannel chan bool) 17 | } 18 | 19 | type RPCMultiplexer struct { 20 | rw io.ReadWriter 21 | OutstandingRequests []*Request 22 | writeChannel chan []byte 23 | handler RPCHandler 24 | } 25 | 26 | func NewRPCMultiplexer(rw io.ReadWriter, handler RPCHandler) *RPCMultiplexer { 27 | return &RPCMultiplexer { 28 | rw: rw, 29 | handler: handler, 30 | } 31 | } 32 | 33 | func (m *RPCMultiplexer) writer() { 34 | for { 35 | buffer, ok := <-m.writeChannel 36 | if !ok { 37 | break 38 | } 39 | err := WriteFrame(m.rw, buffer[0], buffer[1:]) 40 | if err != nil { 41 | fmt.Println("Couldn't write frame", err) 42 | close(m.writeChannel) 43 | break 44 | } 45 | } 46 | } 47 | 48 | func (m *RPCMultiplexer) responseListener(requestId byte, responseChannel chan []byte) { 49 | for { 50 | buffer, ok := <-responseChannel 51 | if !ok { 52 | break 53 | } 54 | m.writeChannel <- addRequestId(requestId, buffer) 55 | } 56 | } 57 | 58 | func (m *RPCMultiplexer) closeListener(requestId byte, closeChannel chan bool) { 59 | _ = <-closeChannel 60 | req := m.OutstandingRequests[requestId] 61 | //fmt.Println("Now going to close stuff for", req) 62 | close(req.requestChannel) 63 | close(req.responseChannel) 64 | close(req.closeChannel) 65 | m.OutstandingRequests[requestId] = nil 66 | } 67 | 68 | func (m *RPCMultiplexer) Multiplex() error { 69 | m.OutstandingRequests = make([]*Request, 255) 70 | m.writeChannel = make(chan []byte) 71 | 72 | go m.writer() 73 | 74 | for { 75 | requestId, buffer, err := ReadFrame(m.rw) 76 | if err != nil { 77 | return err 78 | } 79 | if requestId == 0 { 80 | return errors.New(string(buffer)) 81 | } 82 | req := m.OutstandingRequests[requestId] 83 | if req == nil { 84 | req = &Request { 85 | requestChannel: make(chan []byte, 10), 86 | responseChannel: make(chan []byte, 10), 87 | closeChannel: make(chan bool), 88 | } 89 | m.OutstandingRequests[requestId] = req 90 | go m.responseListener(requestId, req.responseChannel) 91 | go m.closeListener(requestId, req.closeChannel) 92 | go m.handler.handleRequest(req.requestChannel, req.responseChannel, req.closeChannel) 93 | } 94 | req.requestChannel <- buffer 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "fmt" 7 | "flag" 8 | "time" 9 | "strings" 10 | "bytes" 11 | "encoding/json" 12 | "golang.org/x/net/websocket" 13 | "runtime" 14 | ) 15 | 16 | type NoSuchClientError struct { 17 | uuid string 18 | } 19 | 20 | func (e *NoSuchClientError) Error() string { 21 | return fmt.Sprintf("No such client connected: %s", e.uuid) 22 | } 23 | 24 | type WebFSHandler struct { 25 | } 26 | 27 | func quietPanicRecover() { 28 | if r := recover(); r != nil { 29 | fmt.Println("Recovered from panic", r) 30 | } 31 | } 32 | 33 | func (self *WebFSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 34 | defer r.Body.Close() 35 | parts := strings.Split(r.URL.Path, "/") 36 | id := parts[0] 37 | 38 | defer quietPanicRecover() 39 | 40 | req, err := NewClientRequest(id) 41 | 42 | if err != nil { 43 | http.Error(w, err.Error(), http.StatusGone) 44 | return 45 | } 46 | // First send request line 47 | requestLine := fmt.Sprintf("%s %s", r.Method, "/" + strings.Join(parts[1:], "/")) 48 | fmt.Println(requestLine) 49 | req.ch <- []byte(requestLine) 50 | // Then send headers 51 | var headerBuffer bytes.Buffer 52 | for h, v := range r.Header { 53 | headerBuffer.Write([]byte(fmt.Sprintf("%s: %s\n", h, v))) 54 | } 55 | req.ch <- headerBuffer.Bytes() 56 | 57 | // Send body 58 | for { 59 | buffer := make([]byte, BUFFER_SIZE) 60 | n, _ := r.Body.Read(buffer) 61 | if n == 0 { 62 | break 63 | } 64 | req.ch <- buffer[:n] 65 | } 66 | req.ch <- DELIMITERBUFFER 67 | statusCodeBuffer, ok := <-req.ch 68 | if !ok { 69 | http.Error(w, "Connection closed", http.StatusInternalServerError) 70 | return 71 | } 72 | statusCode := BytesToInt(statusCodeBuffer) 73 | headersBuffer, ok := <-req.ch 74 | if !ok { 75 | http.Error(w, "Connection close", http.StatusInternalServerError) 76 | return 77 | } 78 | headers := strings.Split(string(headersBuffer), "\n") 79 | for _, header := range headers { 80 | headerParts := strings.Split(header, ": ") 81 | w.Header().Set(headerParts[0], headerParts[1]) 82 | } 83 | w.WriteHeader(statusCode) 84 | 85 | for { 86 | buffer, ok := <-req.ch 87 | if !ok { 88 | w.Write([]byte("Connection closed")) 89 | break 90 | } 91 | if IsDelimiter(buffer) { 92 | break 93 | } 94 | _, err := w.Write(buffer) 95 | if err != nil { 96 | fmt.Println("Got error", err) 97 | break 98 | } 99 | } 100 | } 101 | 102 | var clients map[string]*Client = make(map[string]*Client) 103 | 104 | type Client struct { 105 | currentRequestId byte 106 | writeChannel chan []byte 107 | pendingRequests []*ClientRequest 108 | } 109 | 110 | func (c *Client) close() { 111 | for i := range(c.pendingRequests) { 112 | if c.pendingRequests[i] != nil { 113 | c.pendingRequests[i].close() 114 | } 115 | } 116 | close(c.writeChannel) 117 | } 118 | 119 | func NewClient(uuid string) *Client { 120 | client := &Client { 121 | writeChannel: make(chan []byte), 122 | pendingRequests: make([]*ClientRequest, 255), 123 | } 124 | clients[uuid] = client 125 | return client 126 | } 127 | 128 | type ClientRequest struct { 129 | requestId byte 130 | // Reusing channel for reading and writing 131 | ch chan []byte 132 | } 133 | 134 | func (cr *ClientRequest) close() { 135 | close(cr.ch) 136 | } 137 | 138 | func addRequestId(requestId byte, buffer []byte) []byte { 139 | newBuffer := make([]byte, len(buffer)+1) 140 | newBuffer[0] = requestId 141 | for i := range buffer { 142 | newBuffer[i+1] = buffer[i] 143 | } 144 | return newBuffer 145 | } 146 | 147 | func NewClientRequest(uuid string) (*ClientRequest, error) { 148 | client, ok := clients[uuid] 149 | if !ok { 150 | return nil, &NoSuchClientError{uuid} 151 | } 152 | client.currentRequestId = (client.currentRequestId + 1) % 255 153 | requestId := client.currentRequestId 154 | req := &ClientRequest { 155 | requestId: requestId, 156 | ch: make(chan []byte), 157 | } 158 | client.pendingRequests[requestId] = req 159 | 160 | go func() { 161 | defer quietPanicRecover() 162 | // Listen on req.ch and move messages over (after 163 | // adding requestId to the write channel 164 | // stop after the delimiter, no more reading will need 165 | // to happen 166 | for { 167 | buffer, ok := <-req.ch 168 | if !ok { 169 | break 170 | } 171 | clients[uuid].writeChannel <- addRequestId(requestId, buffer) 172 | if IsDelimiter(buffer) { 173 | break 174 | } 175 | } 176 | }() 177 | return req, nil 178 | } 179 | 180 | func socketServer(ws *websocket.Conn) { 181 | defer ws.Close() 182 | buffer := make([]byte, BUFFER_SIZE) 183 | n, err := ws.Read(buffer) 184 | var hello HelloMessage 185 | err = json.Unmarshal(buffer[:n], &hello) 186 | if err != nil { 187 | fmt.Println("Could not parse welcome message.") 188 | return 189 | } 190 | fmt.Println("Client", hello.UUID, "connected") 191 | 192 | client := NewClient(hello.UUID) 193 | 194 | closeSocket := func() { 195 | client, ok := clients[hello.UUID]; 196 | if ok { 197 | fmt.Println("Client disconnected", hello.UUID) 198 | client.close() 199 | delete(clients, hello.UUID) 200 | } // else was already closed before 201 | } 202 | 203 | defer closeSocket() 204 | 205 | // Read frame from socket and forward it to request channel 206 | go func() { 207 | defer quietPanicRecover() 208 | for { 209 | requestId, buffer, err := ReadFrame(ws) 210 | if err != nil { 211 | //fmt.Println("Read error", err) 212 | closeSocket() 213 | return 214 | } 215 | req := client.pendingRequests[requestId] 216 | if req == nil { 217 | fmt.Println("Got response for non-existent request", requestId, string(buffer)) 218 | continue 219 | } 220 | req.ch <- buffer 221 | } 222 | }() 223 | 224 | if hello.UserKey != "" { 225 | err := GetEditorClientChannel(hello.UserKey).Send(hello.UUID) 226 | if err != nil { 227 | err = WriteFrame(ws, 0, []byte(err.Error())) 228 | return 229 | } 230 | } 231 | 232 | 233 | for { 234 | writeBuffer, request_ok := <-client.writeChannel 235 | if !request_ok { 236 | return 237 | } 238 | err = WriteFrame(ws, writeBuffer[0], writeBuffer[1:]) 239 | if err != nil { 240 | fmt.Println("Got error", err) 241 | return 242 | } 243 | } 244 | } 245 | 246 | func PrintStats() { 247 | var memStats runtime.MemStats 248 | for { 249 | runtime.ReadMemStats(&memStats) 250 | clientCount := 0 251 | for _ = range clients { 252 | clientCount++ 253 | } 254 | editorClientCount := 0 255 | for _ = range editorClients { 256 | editorClientCount++ 257 | } 258 | fmt.Printf("Editor Clients %d Clients: %d Goroutines: %d Memory: %dK\n", editorClientCount, clientCount, runtime.NumGoroutine(), memStats.Alloc / 1024) 259 | time.Sleep(10e9) // Every 10 seconds 260 | } 261 | } 262 | 263 | func ParseServerFlags(args []string) (ip string, port int, sslCrt string, sslKey string) { 264 | var stats bool 265 | config := ParseConfig() 266 | flagSet := flag.NewFlagSet("zedrem", flag.ExitOnError) 267 | flagSet.StringVar(&ip, "h", config.Server.Ip, "IP to bind to") 268 | flagSet.IntVar(&port, "p", config.Server.Port, "Port to listen on") 269 | flagSet.StringVar(&sslCrt, "sslcrt", config.Server.Sslcert, "Path to SSL certificate") 270 | flagSet.StringVar(&sslKey, "sslkey", config.Server.Sslkey, "Path to SSL key") 271 | flagSet.BoolVar(&stats, "stats", false, "Whether to print go-routine count and memory usage stats periodically.") 272 | flagSet.Parse(args) 273 | if stats { 274 | go PrintStats() 275 | } 276 | flagSet.Parse(args) 277 | return 278 | } 279 | 280 | func RunServer(ip string, port int, sslCrt string, sslKey string, withSignaling bool) { 281 | http.Handle("/fs/", http.StripPrefix("/fs/", &WebFSHandler{})) 282 | http.Handle("/clientsocket", websocket.Handler(socketServer)) 283 | http.Handle("/editorsocket", websocket.Handler(editorSocketServer)) 284 | if sslCrt != "" { 285 | fmt.Printf("Zedrem server now running on wss://%s:%d\n", ip, port) 286 | log.Fatal(http.ListenAndServeTLS(fmt.Sprintf("%s:%d", ip, port), sslCrt, sslKey, nil)) 287 | } else { 288 | fmt.Printf("Zedrem server now running on ws://%s:%d\n", ip, port) 289 | log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", ip, port), nil)) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /zedrem.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "github.com/pborman/uuid" 6 | "strings" 7 | "fmt" 8 | ) 9 | 10 | func main() { 11 | mode := "client" 12 | 13 | if len(os.Args) > 1 && os.Args[1] == "--server" { 14 | mode = "server" 15 | } else if len(os.Args) > 1 && os.Args[1] == "--help" { 16 | mode = "help" 17 | } 18 | 19 | switch mode { 20 | case "server": 21 | ip, port, sslCrt, sslKey := ParseServerFlags(os.Args[2:]) 22 | RunServer(ip, port, sslCrt, sslKey, false) 23 | case "client": 24 | url, userKey, rootPath := ParseClientFlags(os.Args[1:]) 25 | id := strings.Replace(uuid.New(), "-", "", -1) 26 | RunClient(url, id, userKey, rootPath) 27 | case "help": 28 | fmt.Println(`zedrem runs in one of two possible modes: client or server: 29 | 30 | Usage: zedrem [-u url] [-key userKey]