github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/norouter/main.go (about) 1 package main 2 3 import ( 4 "net/http" 5 "net/url" 6 "strings" 7 ) 8 9 /* 10 This is an example how to write Go servers without routers. 11 It can be a useful to know how to do this, however, 12 your life will be probably easier with a router. 13 14 An alternative approach would be to write a switch with matchers: 15 https://www.youtube.com/watch?v=hmq6veCFo0Y 16 */ 17 18 func main() { 19 http.ListenAndServe("127.0.0.1:8080", NewServer()) 20 } 21 22 type ( 23 Server struct { 24 Users EntitiesServer 25 Groups EntitiesServer 26 } 27 EntitiesServer struct { 28 Name string 29 } 30 EntityServer struct { 31 *EntitiesServer 32 ID string 33 } 34 ) 35 36 func NewServer() *Server { 37 server := &Server{} 38 server.Users.Name = "user" 39 server.Groups.Name = "group" 40 return server 41 } 42 43 func (server *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 44 switch prefix, r := Route(r); prefix { 45 case "": 46 server.ServeDashboard(w, r) 47 case "user": 48 server.Users.ServeHTTP(w, r) 49 case "group": 50 server.Groups.ServeHTTP(w, r) 51 default: 52 http.NotFound(w, r) 53 } 54 } 55 56 func (server *Server) ServeDashboard(w http.ResponseWriter, r *http.Request) { 57 ServeBody(w, ` 58 <a href="user/">Users</a> 59 <a href="group/">Groups</a> 60 `) 61 } 62 63 func (server *EntitiesServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 64 switch prefix, r := Route(r); prefix { 65 case "": 66 server.ServeDashboard(w, r) 67 default: 68 // use an ephemeral server for serving subroute 69 // you can also validate ID here 70 (&EntityServer{ 71 EntitiesServer: server, 72 ID: prefix, 73 }).ServeHTTP(w, r) 74 } 75 } 76 77 func (server *EntityServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 78 type R [2]string 79 switch prefix, r := Route(r); (R{r.Method, prefix}) { 80 case R{http.MethodGet, ""}: 81 server.ServeDashboard(w, r) 82 case R{http.MethodGet, "id"}: 83 w.Write([]byte(server.ID)) 84 case R{http.MethodDelete, ""}: 85 http.Error(w, "you are not allowed to do this", http.StatusUnauthorized) 86 default: 87 http.NotFound(w, r) 88 } 89 } 90 91 func (server *EntitiesServer) ServeDashboard(w http.ResponseWriter, r *http.Request) { 92 ServeBody(w, ` 93 <a href="1/">`+server.Name+` 1</a> 94 <a href="2/">`+server.Name+` 2</a> 95 `) 96 } 97 98 func (server *EntityServer) ServeDashboard(w http.ResponseWriter, r *http.Request) { 99 ServeBody(w, ` 100 <a href="id">ID</a> 101 `) 102 } 103 104 // ServeBody is just to demonstrate serving some content. 105 func ServeBody(w http.ResponseWriter, body string) { 106 w.Write([]byte("<html><style>a { display: block }</style><body>" + body + "</html></body>")) 107 } 108 109 func Route(r *http.Request) (prefix string, r2 *http.Request) { 110 path := strings.TrimLeft(r.URL.Path, "/") 111 i := strings.IndexByte(path, '/') 112 if i >= 0 { 113 prefix = path[:i] 114 rawpath := strings.TrimLeft(r.URL.RawPath, "/") 115 if k := strings.IndexByte(rawpath, '/'); k >= 0 { 116 rawpath = rawpath[:k] 117 } 118 r2 = CloneRequest(r) 119 r2.URL.Path = path[i:] 120 r2.URL.RawPath = rawpath 121 return prefix, r2 122 } 123 r2 = CloneRequest(r) 124 r2.URL.Path = "" 125 r2.URL.RawPath = "" 126 return path, r2 127 } 128 129 func CloneRequest(r *http.Request) *http.Request { 130 r2 := new(http.Request) 131 *r2 = *r 132 r2.URL = new(url.URL) 133 *r2.URL = *r.URL 134 return r2 135 }