via

Simple pubsub server inspired by https://patchbay.pub/
git clone https://git.ce9e.org/via.git

commit
0dd16c8ba2b9370902c655e299e807de0f3eca72
parent
87bca7e07eed9ad5e15f54d9ada65a4aa84a9e69
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2020-05-05 04:42
add file storage

Diffstat

M README.md 26 +++++++++++++++++---------
M via.go 102 ++++++++++++++++++++++++++++++++++++++++++++++++++-----------
M via.nginx 4 ++--
M via.service 6 +++---

4 files changed, 106 insertions, 32 deletions


diff --git a/README.md b/README.md

@@ -9,23 +9,31 @@ This is very much inspired by <https://patchbay.pub/> and its clones
    9     9 
   10    10 Start the server:
   11    11 
   12    -1 	via [-v] [port]
   -1    12 	via [-v] [-d storage_dir] [port]
   13    13 
   14    14 Then start sending requests on the client:
   15    15 
   16    16 	# start listening for server sent event stream
   17    -1 	curl http://localhost:8001/someid
   -1    17 	curl http://localhost:8001/msg/someid
   18    18 
   19    -1 	# POST some data
   20    -1 	curl http://localhost:8001/someid -d somedata
   -1    19 	# POST a message
   -1    20 	curl http://localhost:8001/msg/someid -d somedata
   21    21 
   22    -1 You can also protect your ID with a password so no one else can listen to
   23    -1 it at the same time:
   -1    22 	# Store, get, and delete a document
   -1    23 	curl http://localhost:8001/store/someid -d someid
   -1    24 	curl http://localhost:8001/store/someid
   -1    25 	curl http://localhost:8001/store/someid -X DELETE
   24    26 
   25    -1 	curl http://localhost:8001/someid:somepassword
   26    -1 	curl http://localhost:8001/someid  # 403
   27    -1 	curl http://localhost:8001/someid -d somedata  # 200
   -1    27 You can also protect your message ID with a password so no one else can listen
   -1    28 to it at the same time:
   28    29 
   -1    30 	curl http://localhost:8001/msg/someid:somepassword
   -1    31 	curl http://localhost:8001/msg/someid  # 403
   -1    32 	curl http://localhost:8001/msg/someid -d somedata  # 200
   -1    33 
   -1    34 You should regularly clean up old files:
   -1    35 
   -1    36 	find {storage_dir} -type f -mtime +7 -delete
   29    37 
   30    38 ## Differences to patchbay
   31    39 

diff --git a/via.go b/via.go

@@ -1,21 +1,14 @@
    1    -1 // Simple pubsub server inspired by https://patchbay.pub
    2    -1 //
    3    -1 // Usage: via [-v] [port]
    4    -1 // curl http://localhost:8001/someid  # server sent event stream
    5    -1 // curl http://localhost:8001/someid -d somedata
    6    -1 //
    7    -1 // curl http://localhost:8001/someid:somepassword
    8    -1 // curl http://localhost:8001/someid  # 403
    9    -1 // curl http://localhost:8001/someid -d somedata  # 200
   10     1 package main
   11     2 
   12     3 import (
   -1     4 	"encoding/base64"
   13     5 	"flag"
   14     6 	"fmt"
   15     7 	"io/ioutil"
   16     8 	"log"
   17     9 	"net/http"
   18    10 	"os"
   -1    11 	"path"
   19    12 	"strings"
   20    13 	"sync"
   21    14 	"time"
@@ -30,6 +23,7 @@ type Topic struct {
   30    23 var mux = &sync.RWMutex{}
   31    24 var topics = make(map[string]Topic)
   32    25 var verbose = false
   -1    26 var dir = ""
   33    27 
   34    28 func splitPassword(combined string) (string, string) {
   35    29 	split := strings.SplitN(combined, ":", 2)
@@ -83,7 +77,12 @@ func popChannel(key string, ch chan []byte) {
   83    77 	}
   84    78 }
   85    79 
   86    -1 func post(w http.ResponseWriter, r *http.Request) {
   -1    80 func getPath(key string) string {
   -1    81 	hash := base64.URLEncoding.EncodeToString([]byte(key))
   -1    82 	return path.Join(dir, hash)
   -1    83 }
   -1    84 
   -1    85 func postMsg(w http.ResponseWriter, r *http.Request) {
   87    86 	key, password := splitPassword(r.URL.Path)
   88    87 
   89    88 	if password != "" {
@@ -116,7 +115,7 @@ func post(w http.ResponseWriter, r *http.Request) {
  116   115 	}
  117   116 }
  118   117 
  119    -1 func get(w http.ResponseWriter, r *http.Request) {
   -1   118 func getMsg(w http.ResponseWriter, r *http.Request) {
  120   119 	key, password := splitPassword(r.URL.Path)
  121   120 
  122   121 	ch := make(chan []byte)
@@ -158,15 +157,79 @@ func get(w http.ResponseWriter, r *http.Request) {
  158   157 	}
  159   158 }
  160   159 
  161    -1 func handler(w http.ResponseWriter, r *http.Request) {
   -1   160 func putStore(w http.ResponseWriter, r *http.Request) {
   -1   161 	path := getPath(r.URL.Path)
   -1   162 
   -1   163 	content, err := ioutil.ReadAll(r.Body)
   -1   164 	if err != nil {
   -1   165 		log.Println("error reading request body:", err)
   -1   166 		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
   -1   167 		return
   -1   168 	}
   -1   169 
   -1   170 	err = ioutil.WriteFile(path, content, 0644)
   -1   171 	if err != nil {
   -1   172 		log.Println("error writing to file:", path, err)
   -1   173 		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
   -1   174 		return
   -1   175 	}
   -1   176 }
   -1   177 
   -1   178 func getStore(w http.ResponseWriter, r *http.Request) {
   -1   179 	path := getPath(r.URL.Path)
   -1   180 
   -1   181 	content, err := ioutil.ReadFile(path)
   -1   182 	if os.IsNotExist(err) {
   -1   183 		http.Error(w, "Not Found", http.StatusNotFound)
   -1   184 		return
   -1   185 	} else if err != nil {
   -1   186 		log.Println("error reading from file:", path, err)
   -1   187 		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
   -1   188 		return
   -1   189 	}
   -1   190 
   -1   191 	w.Write(content)
   -1   192 }
   -1   193 
   -1   194 func deleteStore(w http.ResponseWriter, r *http.Request) {
   -1   195 	path := getPath(r.URL.Path)
   -1   196 
   -1   197 	err := os.Remove(path)
   -1   198 	if os.IsNotExist(err) {
   -1   199 		http.Error(w, "Not Found", http.StatusNotFound)
   -1   200 		return
   -1   201 	} else if err != nil {
   -1   202 		log.Println("error removing file:", path, err)
   -1   203 		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
   -1   204 		return
   -1   205 	}
   -1   206 }
   -1   207 
   -1   208 func handleMsg(w http.ResponseWriter, r *http.Request) {
  162   209 	if verbose {
  163   210 		log.Println(r.Method, r.URL)
  164   211 	}
  165   212 
  166    -1 	if r.Method == "GET" {
  167    -1 		get(w, r)
  168    -1 	} else if r.Method == "POST" {
  169    -1 		post(w, r)
   -1   213 	if r.Method == http.MethodGet {
   -1   214 		getMsg(w, r)
   -1   215 	} else if r.Method == http.MethodPost {
   -1   216 		postMsg(w, r)
   -1   217 	} else {
   -1   218 		http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
   -1   219 	}
   -1   220 }
   -1   221 
   -1   222 func handleStore(w http.ResponseWriter, r *http.Request) {
   -1   223 	if verbose {
   -1   224 		log.Println(r.Method, r.URL)
   -1   225 	}
   -1   226 
   -1   227 	if r.Method == http.MethodPut || r.Method == http.MethodPost {
   -1   228 		putStore(w, r)
   -1   229 	} else if r.Method == http.MethodGet || r.Method == http.MethodHead {
   -1   230 		getStore(w, r)
   -1   231 	} else if r.Method == http.MethodDelete {
   -1   232 		deleteStore(w, r)
  170   233 	} else {
  171   234 		http.Error(w, "Unsupported Method", http.StatusMethodNotAllowed)
  172   235 	}
@@ -179,14 +242,17 @@ func main() {
  179   242 	}
  180   243 
  181   244 	flag.BoolVar(&verbose, "v", false, "enable verbose logs")
   -1   245 	flag.StringVar(&dir, "d", ".", "directory for storage")
  182   246 	flag.Parse()
  183   247 
  184   248 	addr := "localhost:8001"
  185   249 	if len(flag.Args()) > 0 {
  186    -1 		addr = fmt.Sprintf("localhost:%s", flag.Args()[0]
   -1   250 		addr = fmt.Sprintf("localhost:%s", flag.Args()[0])
  187   251 	}
  188   252 
  189    -1 	http.HandleFunc("/", handler)
   -1   253 	http.HandleFunc("/msg/", handleMsg)
   -1   254 	http.HandleFunc("/store/", handleStore)
   -1   255 
  190   256 	log.Printf("Serving on http://%s", addr)
  191   257 	log.Fatal(http.ListenAndServe(addr, nil))
  192   258 }

diff --git a/via.nginx b/via.nginx

@@ -8,7 +8,7 @@ server {
    8     8 		# https://enable-cors.org/server_nginx.html
    9     9 		if ($request_method = 'OPTIONS') {
   10    10 			add_header 'Access-Control-Allow-Origin' '*';
   11    -1 			add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
   -1    11 			add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, HEAD, OPTIONS';
   12    12 			add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
   13    13 			add_header 'Access-Control-Max-Age' 1728000;
   14    14 			add_header 'Content-Type' 'text/plain; charset=utf-8';
@@ -16,7 +16,7 @@ server {
   16    16 			return 204;
   17    17 		}
   18    18 		add_header 'Access-Control-Allow-Origin' '*';
   19    -1 		add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
   -1    19 		add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, HEAD, OPTIONS';
   20    20 		add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
   21    21 
   22    22 		add_header 'Link' '<https://github.com/xi/via>; rel="help"';

diff --git a/via.service b/via.service

@@ -4,10 +4,10 @@ After=network.target
    4     4 
    5     5 [Service]
    6     6 Type=simple
    7    -1 ExecStart=/usr/bin/via 8001
   -1     7 ExecStart=/usr/bin/via -d /var/via 8001
    8     8 Restart=always
    9    -1 DynamicUser=yes
   10    -1 ProtectSystem=strict
   -1     9 User=via
   -1    10 ProtectSystem=full
   11    11 ProtectHome=yes
   12    12 MemoryDenyWriteExecute=yes
   13    13