|
@@ -0,0 +1,150 @@
|
|
|
|
+package main
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "fmt"
|
|
|
|
+ "io"
|
|
|
|
+ "log"
|
|
|
|
+ "net/http"
|
|
|
|
+ "strconv"
|
|
|
|
+ "sync"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+var (
|
|
|
|
+ version = "0.0-undefined"
|
|
|
|
+ buildtime = "0000-00-00T00:00:00+0000"
|
|
|
|
+ queues = make(map[string]*Queue)
|
|
|
|
+ mut = &sync.Mutex{}
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+type Transfer struct {
|
|
|
|
+ reader io.Reader
|
|
|
|
+ size int
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type Queue struct {
|
|
|
|
+ ch chan Transfer
|
|
|
|
+ producers int
|
|
|
|
+ consumers int
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func NewQueue() *Queue {
|
|
|
|
+ return &Queue{
|
|
|
|
+ ch: make(chan Transfer),
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (q *Queue) addConsumer() {
|
|
|
|
+ q.consumers = q.consumers + 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (q *Queue) remConsumer() {
|
|
|
|
+ q.consumers = q.consumers - 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (q *Queue) addProducer() {
|
|
|
|
+ q.producers = q.producers + 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (q *Queue) remProducer() {
|
|
|
|
+ q.producers = q.producers - 1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (q *Queue) isEmpty() bool {
|
|
|
|
+ return q.producers == 0 && q.consumers == 0
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func main() {
|
|
|
|
+ log.Printf("pathway version:%s buildtime:%s", version, buildtime)
|
|
|
|
+
|
|
|
|
+ handle := func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
+ channelId := r.URL.Path
|
|
|
|
+
|
|
|
|
+ if r.Method == "GET" {
|
|
|
|
+ log.Printf("GET: %s", channelId)
|
|
|
|
+ mut.Lock()
|
|
|
|
+ queue, exists := queues[channelId]
|
|
|
|
+ if !exists {
|
|
|
|
+ queue = NewQueue()
|
|
|
|
+ queues[channelId] = queue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ch := queue.ch
|
|
|
|
+
|
|
|
|
+ queue.addConsumer()
|
|
|
|
+ mut.Unlock()
|
|
|
|
+
|
|
|
|
+ select {
|
|
|
|
+ case transfer := <-ch:
|
|
|
|
+ w.Header().Set("Content-Length", fmt.Sprintf("%d", transfer.size))
|
|
|
|
+ _, err := io.Copy(w, transfer.reader)
|
|
|
|
+ if err != nil {
|
|
|
|
+ if closer, ok := transfer.reader.(io.Closer); ok {
|
|
|
|
+ closer.Close()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ case <-r.Context().Done():
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mut.Lock()
|
|
|
|
+ queue.remConsumer()
|
|
|
|
+ if queue.isEmpty() {
|
|
|
|
+ delete(queues, channelId)
|
|
|
|
+ }
|
|
|
|
+ mut.Unlock()
|
|
|
|
+ } else {
|
|
|
|
+ log.Printf("POST: %s", channelId)
|
|
|
|
+ mut.Lock()
|
|
|
|
+ queue, exists := queues[channelId]
|
|
|
|
+ if !exists {
|
|
|
|
+ queue = NewQueue()
|
|
|
|
+ queues[channelId] = queue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ch := queue.ch
|
|
|
|
+
|
|
|
|
+ queue.addProducer()
|
|
|
|
+ mut.Unlock()
|
|
|
|
+
|
|
|
|
+ reader, writer := io.Pipe()
|
|
|
|
+
|
|
|
|
+ contentLength, err := strconv.Atoi(r.Header.Get("Content-Length"))
|
|
|
|
+ if err != nil {
|
|
|
|
+ contentLength = 0
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ transfer := Transfer{
|
|
|
|
+ reader: reader,
|
|
|
|
+ size: contentLength,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ select {
|
|
|
|
+ case ch <- transfer:
|
|
|
|
+ io.Copy(writer, r.Body)
|
|
|
|
+ case <-r.Context().Done():
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ writer.Close()
|
|
|
|
+
|
|
|
|
+ mut.Lock()
|
|
|
|
+ queue.remProducer()
|
|
|
|
+ if queue.isEmpty() {
|
|
|
|
+ delete(queues, channelId)
|
|
|
|
+ }
|
|
|
|
+ mut.Unlock()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ health := func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
|
+ w.Write([]byte("OK"))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ http.HandleFunc("/health", health)
|
|
|
|
+ http.HandleFunc("/", handle)
|
|
|
|
+
|
|
|
|
+ err := http.ListenAndServe(":8080", nil)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println(err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|