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  }