Compare commits
7 Commits
8aec1e0eab
...
c47d2d7456
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c47d2d7456 | ||
|
|
33f2771014 | ||
|
|
6501934582 | ||
|
|
ab3856a40e | ||
|
|
d5a290c60d | ||
|
|
b6a104526c | ||
|
|
7531918cbe |
@@ -2,9 +2,19 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coder/websocket"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -17,5 +27,119 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run(ctx context.Context) error {
|
func run(ctx context.Context) error {
|
||||||
|
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||||
|
port := fs.Int("p", 8080, "port")
|
||||||
|
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
S := &S{
|
||||||
|
ctx: ctx,
|
||||||
|
limiter: rate.NewLimiter(10, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", *port),
|
||||||
|
Handler: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer can()
|
||||||
|
s.Shutdown(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("listening on", *port)
|
||||||
|
if err := s.ListenAndServe(); err != nil && ctx.Err() == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("shut down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
ctx context.Context
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := s.serveHTTP(w, r); err != nil {
|
||||||
|
log.Println(r.URL.Path, "//", err.Error(), r.Header)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
if isV1(r) || isWS(r) {
|
||||||
|
return s.serveAPI(w, r)
|
||||||
|
}
|
||||||
|
return s.serveStatic(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isV1(r *http.Request) bool {
|
||||||
|
return strings.HasPrefix(r.URL.Path, "/v1/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWS(r *http.Request) bool {
|
||||||
|
return r.URL.Path == "/ws" || strings.HasPrefix(r.URL.Path, "/ws/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveStatic(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return fmt.Errorf("not impl static")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
if err := s.injectContext(w, r); err == io.EOF {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isWS(r) {
|
||||||
|
return s.serveWS(w, r)
|
||||||
|
} else if isV1(r) {
|
||||||
|
return s.serveV1(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) injectContext(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
id, err := r.Cookie("uuid")
|
||||||
|
if err != nil || id.Value == "" {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = context.WithValue(ctx, "session", Session{
|
||||||
|
ID: id.Value,
|
||||||
|
})
|
||||||
|
*r = *r.WithContext(ctx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveWS(httpw http.ResponseWriter, httpr *http.Request) error {
|
||||||
|
ctx := httpr.Context()
|
||||||
|
|
||||||
|
c, err := websocket.Accept(httpw, httpr, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.CloseNow()
|
||||||
|
|
||||||
|
if err := c.Write(ctx, 1, []byte("hello world")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveV1(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return fmt.Errorf("not impl: v1")
|
||||||
|
}
|
||||||
|
|||||||
182
cmd/testapi/main.go
Normal file
182
cmd/testapi/main.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coder/websocket"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
if err := run(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context) error {
|
||||||
|
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||||
|
port := fs.Int("p", 8080, "port")
|
||||||
|
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
S := &S{
|
||||||
|
ctx: ctx,
|
||||||
|
limiter: rate.NewLimiter(10, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", *port),
|
||||||
|
Handler: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
defer can()
|
||||||
|
s.Shutdown(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("listening on", *port)
|
||||||
|
if err := s.ListenAndServe(); err != nil && ctx.Err() == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("shut down")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
ctx context.Context
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := s.serveHTTP(w, r); err != nil {
|
||||||
|
log.Println(r.URL.Path, "//", err.Error())
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
if isV1(r) || isWS(r) {
|
||||||
|
return s.serveAPI(w, r)
|
||||||
|
}
|
||||||
|
return s.serveStatic(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isV1(r *http.Request) bool {
|
||||||
|
return strings.HasPrefix(r.URL.Path, "/v1/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWS(r *http.Request) bool {
|
||||||
|
return r.URL.Path == "/ws" || strings.HasPrefix(r.URL.Path, "/ws/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveStatic(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return fmt.Errorf("not impl static")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveAPI(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
if err := s.injectContext(w, r); err == io.EOF {
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isWS(r) {
|
||||||
|
return s.serveWS(w, r)
|
||||||
|
} else if isV1(r) {
|
||||||
|
return s.serveV1(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) injectContext(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
id, err := r.Cookie("uuid")
|
||||||
|
if err != nil || id.Value == "" {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = context.WithValue(ctx, "session", Session{
|
||||||
|
ID: id.Value,
|
||||||
|
})
|
||||||
|
*r = *r.WithContext(ctx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) Session(ctx context.Context) Session {
|
||||||
|
v, _ := ctx.Value("session").(Session)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveWS(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
c, err := websocket.Accept(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.CloseNow()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/ws/page/a":
|
||||||
|
if err := c.Write(ctx, 1, []byte(`{"page":"a", "items": [{"name":"x"}, {"name":"y"}]}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "/ws/page/a/event/a":
|
||||||
|
if err := c.Write(ctx, 1, []byte(`{"page":"a", "event":"a", "items": [{"name":"x"}, {"name":"y"}]}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "/ws/page/b/event/a":
|
||||||
|
if err := c.Write(ctx, 1, []byte(`{"page":"b", "event":"a", "items": [{"name":"x", "title":"x1", "tags":[{"k":"k", "v":"v"}, {"k":"K", "v":"V"}, {"k":"kkk","v":"vvv"}]}, {"name":"y", "tags":[{"k":"k", "v":"v"}, {"k":"K", "v":"V"}, {"k":"kkk","v":"vvv"}]}], "help": "hello world"}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "/ws/page/b/event/b":
|
||||||
|
if err := c.Write(ctx, 1, []byte(`{"page":"b", "event":"b", "items": [{"name":"x", "title":"x1", "tags":[{"k":"k", "v":"v"}, {"k":"K", "v":"V"}, {"k":"kkk","v":"vvv"}]}, {"name":"y", "tags":[{"k":"k", "v":"v"}, {"k":"K", "v":"V"}, {"k":"kkk","v":"vvv"}]}], "help": "hello world"}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := c.Write(ctx, 1, []byte(`hello world`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S) serveV1(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
ctx := r.Context()
|
||||||
|
switch r.Method + r.URL.Path {
|
||||||
|
case "PUT/v1/state/" + s.Session(ctx).ID + "/party":
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
35
cmd/testws/main.go
Normal file
35
cmd/testws/main.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/coder/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
c, _, err := websocket.Dial(ctx, os.Args[1], &websocket.DialOptions{
|
||||||
|
HTTPHeader: map[string][]string{
|
||||||
|
"Cookie": []string{"uuid=x"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer c.CloseNow()
|
||||||
|
|
||||||
|
for {
|
||||||
|
mt, b, err := c.Read(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[%s] %s", mt, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
4
go.mod
4
go.mod
@@ -1,3 +1,7 @@
|
|||||||
module gitea.inhome.blapointe.com/bel/out
|
module gitea.inhome.blapointe.com/bel/out
|
||||||
|
|
||||||
go 1.22.3
|
go 1.22.3
|
||||||
|
|
||||||
|
require golang.org/x/time v0.8.0
|
||||||
|
|
||||||
|
require github.com/coder/websocket v1.8.12 // indirect
|
||||||
|
|||||||
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||||
|
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
Reference in New Issue
Block a user