/* * Copyright IDMesh Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "errors" "fmt" "log" "net/http" ) const ( CookieKeyIDMeshUserID = "idmesh_user_id" ) type Config struct { // MUST, default redirect url DefaultRedirectURL string `json:"default_redirect_url"` // OPTIONAL, map[user_id]redirect_url, fallback: default_redirect_url Rules map[string]string `json:"rules"` } func (cfg *Config) Validate() error { if cfg.DefaultRedirectURL == "" { return errors.New("empty `default_redirect_url`") } return nil } type RelayServer struct { cfg *Config } func NewRelayServer(cfg Config) (http.Handler, error) { if err := cfg.Validate(); err != nil { return nil, err } return &RelayServer{cfg: &cfg}, nil } func (s *RelayServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.printRequest(r) switch r.URL.Path { case "", "/": uid, err := s.readUserID(r) if err != nil { log.Println(err) s.redirect(w, s.cfg.DefaultRedirectURL) return } loc, ok := s.cfg.Rules[uid] if !ok { log.Println("unexpected uid:", uid, ", fallback to default url:", s.cfg.DefaultRedirectURL) s.redirect(w, s.cfg.DefaultRedirectURL) return } s.redirect(w, loc) default: w.WriteHeader(http.StatusNotFound) } } func (s *RelayServer) printRequest(r *http.Request) { log.Println("--- request start ---") log.Println("path:") log.Println(r.URL) log.Println("headers:") for k := range r.Header { log.Println(k, "=", r.Header.Get(k)) } log.Println("cookies:") for _, c := range r.Cookies() { log.Println(c.String()) } log.Println("--- request end ---") } func (s *RelayServer) redirect(w http.ResponseWriter, loc string) { nr, err := http.NewRequest(http.MethodGet, loc, nil) if err != nil { log.Printf("fail to make request: %v", err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("internal error")) return } http.Redirect(w, nr, loc, http.StatusFound) } func (s *RelayServer) readUserID(r *http.Request) (string, error) { c, err := r.Cookie(CookieKeyIDMeshUserID) if err != nil { if errors.Is(err, http.ErrNoCookie) { return "", fmt.Errorf("read user: cookie %q not found", CookieKeyIDMeshUserID) } return "", fmt.Errorf("read user: fail to read cookie: %v", err) } return c.Value, nil }