2022-12-02 14:31:43 +08:00
|
|
|
/*
|
|
|
|
* 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 (
|
2023-03-30 15:58:02 +08:00
|
|
|
CookieKeyIDMeshUserID = "idmesh_user_id"
|
2022-12-02 14:31:43 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
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) {
|
2022-12-03 00:59:43 +08:00
|
|
|
s.printRequest(r)
|
|
|
|
|
2022-12-02 14:31:43 +08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-03 00:59:43 +08:00
|
|
|
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 ---")
|
|
|
|
}
|
|
|
|
|
2022-12-02 14:31:43 +08:00
|
|
|
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
|
|
|
|
}
|