package main import ( "encoding/json" "github.com/caarlos0/env/v6" "log" "meow.tf/go/linkinfo" "net" "net/http" "os" "time" ) var ( api *linkinfo.LinkInfoApi ) type apiConfig struct { LocalAddress string `env:"LOCAL_ADDRESS"` ImgurClientId string `env:"IMGUR_CLIENT_ID"` YoutubeKey string `env:"YOUTUBE_KEY"` TwitterConsumerKey string `env:"TWITTER_CONSUMER_KEY"` TwitterConsumerSecret string `env:"TWITTER_CONSUMER_SECRET"` TwitterToken string `env:"TWITTER_TOKEN"` TwitterTokenSecret string `env:"TWITTER_TOKEN_SECRET"` } func main() { config := &apiConfig{} if err := env.Parse(config); err != nil { log.Fatalln("Unable to load config from env") } opts := make([]linkinfo.Option, 0) if config.ImgurClientId != "" { opts = append(opts, &linkinfo.ImgurOptions{ ClientId: config.ImgurClientId, }) } if config.YoutubeKey != "" { opts = append(opts, &linkinfo.YoutubeOptions{ Key: config.YoutubeKey, }) } if config.TwitterConsumerKey != "" { opts = append(opts, &linkinfo.TwitterOptions{ ConsumerKey: config.TwitterConsumerKey, ConsumerSecret: config.TwitterConsumerSecret, Token: config.TwitterToken, TokenSecret: config.TwitterTokenSecret, }) } api = linkinfo.New(opts...) if addr := os.Getenv("LOCAL_ADDRESS"); addr != "" { var err error api.Client, err = constructBoundClient(addr) if err != nil { log.Fatalln("Unable to bind client to address:", err) } } mux := http.NewServeMux() mux.HandleFunc("/info", handleInfoRequest) http.ListenAndServe(":8080", mux) } func handleInfoRequest(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() urlStr := query.Get("url") if urlStr == "" { w.WriteHeader(http.StatusBadRequest) return } ret, err := api.Retrieve(urlStr) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(ret) if err != nil { w.WriteHeader(http.StatusInternalServerError) } } func constructBoundClient(addr string) (*http.Client, error) { localAddr, err := net.ResolveIPAddr("ip", addr) if err != nil { return nil, err } // You also need to do this to make it work and not give you a // "mismatched local address type ip" // This will make the ResolveIPAddr a TCPAddr without needing to // say what SRC port number to use. localTCPAddr := net.TCPAddr{ IP: localAddr.IP, } dialer := &net.Dialer{ LocalAddr: &localTCPAddr, Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, } return &http.Client{ Transport: &http.Transport{ DialContext: dialer.DialContext, }, }, nil }