func getObject(rw http.ResponseWriter, req *http.Request) { obj, err := models.GetObject(userId(req), userKey(req), bucketId(req), objectId(req)) if err != nil { lumber.Error("Get Object: Get :%s", err.Error()) rw.WriteHeader(422) return } if obj.Size == 0 { lumber.Info("object size is 0", obj.Size) rw.WriteHeader(422) rw.Write([]byte("incomplete file")) return } rc, err := obj.ReadCloser() if err != nil { lumber.Error("Get Object: Get ReadCloser :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } defer rc.Close() rw.Header().Set("Content-Type", "application/octet-stream") _, err = io.Copy(rw, rc) if err != nil { lumber.Error("Get Object: Copy :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } }
func deleteObject(rw http.ResponseWriter, req *http.Request) { obj, err := models.GetObject(userId(req), userKey(req), bucketId(req), objectId(req)) if err != nil { lumber.Error("Delete Object: Get :%s", err.Error()) rw.WriteHeader(http.StatusAccepted) return } err = obj.Remove() if err != nil && !strings.Contains(err.Error(), "no such file") { lumber.Error("Delete Object: Remove :%s", err.Error()) // if i cant remove it im assuming it was already gone // we will probably need to check on this sometime soon // to confirm we are not leaving cruft if obj.Size != 0 { // if the object size is 0 dont worry about a failed remove // chances are the object didnt have any data in it. rw.WriteHeader(http.StatusInternalServerError) return } } err = models.DeleteObject(userId(req), userKey(req), obj.BucketID, obj.ID) if err != nil { lumber.Error("Delete Object: Delete :%s", err.Error()) rw.WriteHeader(http.StatusAccepted) return } rw.WriteHeader(http.StatusAccepted) }
func setObjectPublic(rw http.ResponseWriter, req *http.Request) { obj, err := models.GetObject(userId(req), userKey(req), bucketId(req), objectId(req)) if err != nil { lumber.Error("SetObjectPublic: Get :%s", err.Error()) rw.WriteHeader(http.StatusNotFound) return } obj.Public = true err = models.SaveObject(obj) if err != nil { lumber.Error("SetObjectPublic: Save :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } bytes, err := json.Marshal(obj) if err != nil { lumber.Error("SetObjectPublic: Json Marshal :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "application/json") rw.Write(bytes) }
func adminAccess(fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { userId := req.Header.Get("Userid") userKey := req.Header.Get("Key") if userId == "" || userKey == "" { lumber.Error("no userid or key given") rw.WriteHeader(http.StatusNotFound) return } // get a user and return it user, err := models.GetUser(userId) if err != nil { lumber.Error("get user: %s", err.Error()) } if user == nil || user.Key != userKey || user.Admin == false { lumber.Error("User not authorized %+v", user) rw.WriteHeader(http.StatusNotFound) return } fn(rw, req) } }
func main() { setLogLevel() runtime.GOMAXPROCS(runtime.NumCPU()) var be models.Storage switch selectedBackend { case "local": be = backends.NewLocalStorage(backendCredentials) default: be = backends.NewLocalStorage(backendCredentials) } err := models.Initialize(dbCredentials, be) if err != nil { panic(err) } models.CleanEmptyObjects() err = api.Start(port) lumber.Error(err.Error()) }
func startHoarder(ccmd *cobra.Command, args []string) error { // convert the log level logLvl := lumber.LvlInt(viper.GetString("log-level")) // configure the logger lumber.Prefix("[hoader]") lumber.Level(logLvl) // enable/start garbage collection if age config was changed if ccmd.Flag("clean-after").Changed { lumber.Debug("Starting garbage collector (data older than %vs)...\n", ccmd.Flag("clean-after").Value) // start garbage collector go collector.Start() } // set, and initialize, the backend driver if err := backends.Initialize(); err != nil { lumber.Error("Failed to initialize backend - %v", err) return err } // start the API if err := api.Start(); err != nil { lumber.Fatal("Failed to start API: ", err.Error()) return err } return nil }
func (c *Client) Send() { for msg := range c.messages { log.Trace("Sending message: %s", msg) if err := websocket.Message.Send(c.ws, msg); err != nil { log.Error("Error sending message: %s", err) } } }
func listBuckets(rw http.ResponseWriter, req *http.Request) { bucks, err := models.ListBuckets(userId(req), userKey(req)) if err != nil { lumber.Error("List Bucket :%s", err.Error()) rw.WriteHeader(http.StatusNotFound) return } b, err := json.Marshal(bucks) if err != nil { lumber.Error("List Bucket: Parse Json :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "application/json") rw.Write(b) }
func listObjects(rw http.ResponseWriter, req *http.Request) { objs, err := models.ListObjects(userId(req), userKey(req), bucketId(req)) if err != nil { lumber.Error("List Object: Get :%s", err.Error()) rw.WriteHeader(422) return } b, err := json.Marshal(objs) if err != nil { lumber.Error("List Object: Json Marshal :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "application/json") rw.Write(b) }
// PublishAfter sends a message after [delay] func (p *Proxy) PublishAfter(tags []string, data string, delay time.Duration) { go func() { <-time.After(delay) if err := publish(p.id, tags, data); err != nil { // log this error and continue lumber.Error("Proxy failed to PublishAfter - %v", err) } }() }
func listUsers(rw http.ResponseWriter, req *http.Request) { users, err := models.ListUsers() if err != nil { lumber.Error("List User: Get :%s", err.Error()) rw.WriteHeader(http.StatusNotFound) return } b, err := json.Marshal(users) if err != nil { lumber.Error("List User: Json Marshel :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "application/json") rw.Write(b) }
// parseBody parses the json body into v func parseBody(req *http.Request, v interface{}) error { // read the body b, err := ioutil.ReadAll(req.Body) if err != nil { lumber.Error(err.Error()) return BodyReadFail } defer req.Body.Close() // parse body and store in v err = json.Unmarshal(b, v) if err != nil { lumber.Error(err.Error()) return BadJson } return nil }
func (t *Server) handlePrefix(id string, msg PrefixMsg) { log.Trace("Handling prefix message") if _, ok := t.prefixes[id]; !ok { t.prefixes[id] = make(PrefixMap) } if err := t.prefixes[id].RegisterPrefix(msg.Prefix, msg.URI); err != nil { log.Error("Error registering prefix: %s", err) } log.Debug("Client %s registered prefix '%s' for URI: %s", id, msg.Prefix, msg.URI) }
func deleteBucket(rw http.ResponseWriter, req *http.Request) { err := models.DeleteBucket(userId(req), userKey(req), bucketId(req)) if err != nil { lumber.Error("Delete Bucket :%s", err.Error()) rw.WriteHeader(http.StatusNotAcceptable) return } rw.WriteHeader(http.StatusAccepted) }
// runLoop handles communication from the server func (relay *Relay) runLoop(reader *bufio.Reader) { for { // when implementing relay, set `lumber.Level(lumber.LvlInt("TRACE"))` in client to view logs line, err := reader.ReadString('\n') if err != nil { lumber.Error("[PULSE :: RELAY] Disconnected from host %v!", relay.hostAddr) // retry indefinitely for { if reader, err = relay.establishConnection(); err == nil { lumber.Info("[PULSE :: RELAY] Reconnected to host %v!", relay.hostAddr) break } lumber.Debug("[PULSE :: RELAY] Reconnecting to host %v... Fail!", relay.hostAddr) <-time.After(5 * time.Second) } // we won't have anything in 'line' so continue continue } line = strings.TrimSuffix(line, "\n") split := strings.SplitN(line, " ", 2) cmd := split[0] switch cmd { case "ok": lumber.Trace("[PULSE :: RELAY] OK: %v", split) // just an ack case "get": lumber.Trace("[PULSE :: RELAY] GET: %v", split) if len(split) != 2 { continue } stats := strings.Split(split[1], ",") results := make([]string, 0) for _, stat := range stats { tagCollector, ok := relay.collectors[stat] if !ok { continue } for name, value := range tagCollector.collector.Collect() { formatted := strconv.FormatFloat(value, 'f', 4, 64) if name == "" { name = stat } results = append(results, fmt.Sprintf("%s-%s:%s", stat, name, formatted)) } } response := fmt.Sprintf("got %s\n", strings.Join(results, ",")) relay.conn.Write([]byte(response)) default: lumber.Trace("[PULSE :: RELAY] BAD: %v", split) relay.conn.Write([]byte("unknown command\n")) } } }
// PublishAfter publishes to ALL subscribers. Usefull in client applications // who reuse the publish connection for subscribing func PublishAfter(tags []string, data string, delay time.Duration) error { go func() { <-time.After(delay) if err := Publish(tags, data); err != nil { // log this error and continue? lumber.Error("Failed to PublishAfter - %v", err) } }() return nil }
func deleteUser(rw http.ResponseWriter, req *http.Request) { id := req.URL.Query().Get(":id") err := models.DeleteUser(id) if err != nil { lumber.Error("Delete User: Delete :%s", err.Error()) rw.WriteHeader(http.StatusNotAcceptable) return } rw.WriteHeader(http.StatusAccepted) }
func createBucket(rw http.ResponseWriter, req *http.Request) { buck, err := models.CreateBucket(userId(req), userKey(req), bucketId(req)) if err != nil { lumber.Error("New Bucket :%s", err.Error()) rw.WriteHeader(422) return } b, _ := json.Marshal(buck) rw.WriteHeader(http.StatusCreated) rw.Header().Set("Content-Type", "application/json") rw.Write(b) }
func createUser(rw http.ResponseWriter, req *http.Request) { s := sizeLimit(req) newUser, err := models.CreateUser(s) if err != nil { lumber.Error("Create User: Create :%s", err.Error()) rw.WriteHeader(422) return } b, _ := json.Marshal(newUser) rw.WriteHeader(http.StatusCreated) rw.Header().Set("Content-Type", "application/json") rw.Write(b) }
func getObjectInfo(rw http.ResponseWriter, req *http.Request) { obj, err := models.GetObject(userId(req), userKey(req), bucketId(req), objectId(req)) if err != nil { lumber.Error("Get Object Info: Get :%s", err.Error()) rw.WriteHeader(http.StatusNotFound) return } rw.Header().Set("Object-Alias", obj.Alias) rw.Header().Set("Object-Size", strconv.FormatInt(obj.Size, 10)) rw.Header().Set("Object-Checksum", obj.CheckSum) if req.Method == "GET" { bytes, err := json.Marshal(obj) if err != nil { lumber.Error("Get Object Info: Json Marshal :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "application/json") rw.Write(bytes) } }
func (c *Client) Listen() { for { var rec string err := websocket.Message.Receive(c.ws, &rec) if err != nil { if err != io.EOF { log.Error("Error receiving message, aborting connection: %s", err) } break } log.Trace("Message received: %s", rec) data := []byte(rec) switch typ := ParseType(rec); typ { case CALLRESULT: var msg CallResultMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling call result message: %s", err) } c.handleCallResult(msg) case CALLERROR: var msg CallErrorMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling call error message: %s", err) } c.handleCallError(msg) case EVENT: var msg EventMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling event message: %s", err) } c.handleEvent(msg) case PREFIX, CALL, SUBSCRIBE, UNSUBSCRIBE, PUBLISH: log.Error("Client -> server message received, ignored: %s", TypeString(typ)) case WELCOME: log.Error("Received extraneous welcome message, ignored") default: log.Error("Invalid message format, message dropped: %s", data) } } }
// connect dials the remote mist server and handles any incoming responses back // from mist func (c *TCP) connect() error { // attempt to connect to the server conn, err := net.Dial("tcp", c.host) if err != nil { return fmt.Errorf("Failed to dial '%v' - %v", c.host, err) } // set the connection for the client c.conn = conn // create a new json encoder for the clients connection c.encoder = json.NewEncoder(c.conn) // if the client was created with a token, authentication is needed if c.token != "" { err = c.encoder.Encode(&mist.Message{Command: "auth", Data: c.token}) if err != nil { return fmt.Errorf("Failed to send auth - %v", err) } } // ensure we are authorized/still connected (unauthorized clients get disconnected) c.Ping() decoder := json.NewDecoder(conn) msg := mist.Message{} if err := decoder.Decode(&msg); err != nil { conn.Close() close(c.messages) return fmt.Errorf("Ping failed, possibly bad token, or can't read from mist") } // connection loop (blocking); continually read off the connection. Once something // is read, check to see if it's a message the client understands to be one of // its commands. If so attempt to execute the command. go func() { for { msg := mist.Message{} // decode an array value (Message) if err := decoder.Decode(&msg); err != nil { switch err { case io.EOF: lumber.Debug("[mist client] Mist terminated connection") case io.ErrUnexpectedEOF: lumber.Debug("[mist client] Mist terminated connection unexpedtedly") default: lumber.Error("[mist client] Failed to get message from mist - %s", err.Error()) } conn.Close() close(c.messages) return } c.messages <- msg // read from this using the .Messages() function lumber.Trace("[mist client] Received message - %#v", msg) } }() return nil }
func createObject(rw http.ResponseWriter, req *http.Request) { _, err := models.GetObject(userId(req), userKey(req), bucketId(req), objectId(req)) // If the object already exists replace it if err == nil { replaceObject(rw, req) return } obj, err := models.CreateObject(userId(req), userKey(req), bucketId(req), objectId(req)) if err != nil { lumber.Error("Create Object: Create :%s", err.Error()) rw.WriteHeader(422) return } w, err := obj.WriteCloser() if err != nil { lumber.Error("Create Object: Get writecloser :%s", err.Error()) models.DeleteObject(userId(req), userKey(req), obj.BucketID, obj.ID) rw.WriteHeader(http.StatusInternalServerError) return } defer w.Close() defer req.Body.Close() hash := md5.New() multiWriter := io.MultiWriter(hash, w) size, err := io.Copy(multiWriter, req.Body) if err != nil { lumber.Error("Create Object: Copy :%s", err.Error()) if err = obj.Remove(); err == nil { models.DeleteObject(userId(req), userKey(req), obj.BucketID, obj.ID) } rw.WriteHeader(http.StatusInternalServerError) return } // obj.Size = int64(size) obj.Public = req.Header.Get("Public") == "true" obj.CheckSum = fmt.Sprintf("%x", hash.Sum(nil)) err = models.SaveObject(obj) if err != nil { lumber.Error("Create Object: Save :%s", err.Error()) if err = obj.Remove(); err == nil { models.DeleteObject(userId(req), userKey(req), obj.BucketID, obj.ID) } rw.WriteHeader(http.StatusInternalServerError) return } if !obj.Exists() { lumber.Error("Create Object: Confirm file: File didnt exist on the file system") models.DeleteObject(userId(req), userKey(req), obj.BucketID, obj.ID) rw.WriteHeader(http.StatusInternalServerError) return } // get the user and make sure we are still in the limit usr, err := models.GetUser(userId(req)) if err == nil && usr.LimitExceeded() { lumber.Error("Create Object: LimitExceeded") if err = obj.Remove(); err == nil { models.DeleteObject(userId(req), userKey(req), obj.BucketID, obj.ID) } rw.WriteHeader(http.StatusNotAcceptable) return } f, _ := json.Marshal(obj) rw.WriteHeader(http.StatusCreated) rw.Header().Set("Content-Type", "application/json") rw.Write(f) }
func replaceObject(rw http.ResponseWriter, req *http.Request) { obj, err := models.GetObject(userId(req), userKey(req), bucketId(req), objectId(req)) if err != nil { lumber.Error("Replace Object: Get Existing :%s", err.Error()) rw.WriteHeader(http.StatusNotFound) return } // create a new temporary file // the new file has an alias that is the id of the old object tmpObj, err := models.CreateObject(userId(req), userKey(req), obj.BucketID, obj.ID) if err != nil { lumber.Error("Replace Object: New Object :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } // write to the tmp file w, err := tmpObj.WriteCloser() if err != nil { lumber.Error("Replace Object: Write to tmp :%s", err.Error()) models.DeleteObject(userId(req), userKey(req), tmpObj.BucketID, tmpObj.ID) rw.WriteHeader(http.StatusInternalServerError) return } defer w.Close() defer req.Body.Close() hash := md5.New() multiWriter := io.MultiWriter(hash, w) size, err := io.Copy(multiWriter, req.Body) if err != nil { lumber.Error("Replace Object: Copy to tmp :%s", err.Error()) if err = tmpObj.Remove(); err == nil { models.DeleteObject(userId(req), userKey(req), tmpObj.BucketID, tmpObj.ID) } rw.WriteHeader(http.StatusInternalServerError) return } // before we move lets make sure the existing object exists on the system old, err := obj.WriteCloser() if err != nil { lumber.Error("Replace Object: Create old if not exists :%s", err.Error()) models.DeleteObject(userId(req), userKey(req), tmpObj.BucketID, tmpObj.ID) rw.WriteHeader(http.StatusInternalServerError) return } old.Close() // move the tmp object to the existing one err = tmpObj.Move(obj.ID) if err != nil { lumber.Error("Replace Object: Move Tmp :%s", err.Error()) if err = tmpObj.Remove(); err == nil { models.DeleteObject(userId(req), userKey(req), tmpObj.BucketID, tmpObj.ID) } rw.WriteHeader(http.StatusInternalServerError) return } // I have no need for the db record anymore models.DeleteObject(userId(req), userKey(req), tmpObj.BucketID, tmpObj.ID) // set size of replaced object obj.Size = int64(size) obj.Public = req.Header.Get("Public") == "true" obj.CheckSum = fmt.Sprintf("%x", hash.Sum(nil)) err = models.SaveObject(obj) if err != nil { lumber.Error("Replace Object: Save Existing :%s", err.Error()) rw.WriteHeader(http.StatusInternalServerError) return } if !obj.Exists() { lumber.Error("Replace Object: Confirm file: File didnt exist on the file system") models.DeleteObject(userId(req), userKey(req), obj.BucketID, obj.ID) rw.WriteHeader(http.StatusInternalServerError) return } f, _ := json.Marshal(obj) rw.WriteHeader(http.StatusCreated) rw.Header().Set("Content-Type", "application/json") rw.Write(f) }
func (t *Server) HandleWebsocket(conn *websocket.Conn) { defer conn.Close() log.Debug("Received websocket connection") tid, err := uuid.NewV4() if err != nil { log.Error("Could not create unique id, refusing client connection") return } id := tid.String() arr, err := CreateWelcome(id, TURNPIKE_SERVER_IDENT) if err != nil { log.Error("Error encoding welcome message") return } log.Debug("Sending welcome message: %s", arr) err = websocket.Message.Send(conn, string(arr)) if err != nil { log.Error("Error sending welcome message, aborting connection: %s", err) return } c := make(chan string, serverBacklog) t.clients[id] = c go func() { for msg := range c { log.Trace("Sending message: %s", msg) err := websocket.Message.Send(conn, string(msg)) if err != nil { log.Error("Error sending message: %s", err) } } }() for { var rec string err := websocket.Message.Receive(conn, &rec) if err != nil { if err != io.EOF { log.Error("Error receiving message, aborting connection: %s", err) } break } log.Trace("Message received: %s", rec) data := []byte(rec) switch typ := ParseType(rec); typ { case PREFIX: var msg PrefixMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling prefix message: %s", err) } t.handlePrefix(id, msg) case CALL: var msg CallMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling call message: %s", err) } t.handleCall(id, msg) case SUBSCRIBE: var msg SubscribeMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling subscribe message: %s", err) } t.handleSubscribe(id, msg) case UNSUBSCRIBE: var msg UnsubscribeMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling unsubscribe message: %s", err) } t.handleUnsubscribe(id, msg) case PUBLISH: var msg PublishMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling publish message: %s", err) } t.handlePublish(id, msg) case WELCOME, CALLRESULT, CALLERROR, EVENT: log.Error("Server -> client message received, ignored: %s", TypeString(typ)) default: log.Error("Invalid message format, message dropped: %s", data) } } delete(t.clients, id) close(c) }
func (t *Server) handlePublish(id string, msg PublishMsg) { log.Trace("Handling publish message") topic := CheckCurie(t.prefixes[id], msg.TopicURI) lm, ok := t.subscriptions[topic] if !ok { return } out, err := CreateEvent(topic, msg.Event) if err != nil { log.Error("Error creating event message: %s", err) return } var sendTo []string if len(msg.ExcludeList) > 0 || len(msg.EligibleList) > 0 { // this is super ugly, but I couldn't think of a better way... for tid := range lm { include := true for _, _tid := range msg.ExcludeList { if tid == _tid { include = false break } } if include { sendTo = append(sendTo, tid) } } for _, tid := range msg.EligibleList { include := true for _, _tid := range sendTo { if _tid == tid { include = false break } } if include { sendTo = append(sendTo, tid) } } } else { for tid := range lm { if tid == id && msg.ExcludeMe { continue } sendTo = append(sendTo, tid) } } log.Debug("Sending event messages") for _, tid := range sendTo { // we're not locking anything, so we need // to make sure the client didn't disconnecct in the // last few nanoseconds... if client, ok := t.clients[tid]; ok { client <- string(out) } } }
func main() { var db DB var err error switch dbType { case "fs": db, err = NewFileStore("file_store") case "couchdb": db, err = NewCouchDB("localhost:5984", "test", "", "") default: err = fmt.Errorf("Unsupported database type: %s", dbType) } if err != nil { log.Error("Error initializing database: %s", err) os.Exit(1) return } m := martini.Classic() m.Use(martini.Static("build/web")) m.Get("/projects", func() (string, int) { projs, err := db.GetProjects() if err != nil { return err.Error(), 500 } b, _ := json.Marshal(projs) return string(b), 200 }) m.Get("/projects/:project/tests", func(params martini.Params) (string, int) { tests, err := db.GetTests(params["project"]) if err != nil { return err.Error(), 500 } b, _ := json.Marshal(tests) return string(b), 200 }) m.Post("/projects/:project/tests", func(params martini.Params, r *http.Request) (string, int) { id := time.Now().Format(time.RFC3339) s, err := stein.Parse(r.Body) if err != nil { return err.Error(), 500 } err = db.Save(params["project"], id, s) if err != nil { return err.Error(), 500 } return id, 200 }) m.Get("/projects/:project/tests/:test", func(params martini.Params) (string, int) { s, err := db.GetTest(params["project"], params["test"]) if err != nil { return err.Error(), 500 } b, _ := json.Marshal(s) return string(b), 200 }) m.Run() }
// Start attempts to individually start mist servers from a list of provided // listeners; the listeners provided is a comma delimited list of uri strings // (scheme:[//[user:pass@]host[:port]][/]path[?query][#fragment]) func Start(uris []string, token string) error { // BUG: https://github.com/spf13/viper/issues/112 // due to the above issue with cobra/viper (pflag) when --listeners are provided // we have to parse this string slice manually and then split it into the slice // of string schemes it should have been in the first place; one day this bug // will get fixed and this will probably break... at that point this should be // removed if viper.GetString("config") == "" { r := strings.NewReplacer("[", "", "]", "") uris = strings.Split(r.Replace(uris[0]), ",") } // check to see if a token is provided; an authenticator cannot work without // a token and so it should error here informing that. if auth.DefaultAuth != nil && token == "" { return fmt.Errorf("An authenticator has been specified but no token provided!\n") } // set the authtoken authtoken = token // this chan is given to each individual server start as a way for them to // communcate back their startup status errChan := make(chan error, len(uris)) // iterate over each of the provided listener uris attempting to start them // individually; if one isn't supported it gets skipped for i := range uris { // parse the uri string into a url object url, err := url.Parse(uris[i]) if err != nil { return err } // check to see if the scheme is supported; if not, indicate as such and // continue server, ok := servers[url.Scheme] if !ok { lumber.Error("Unsupported scheme '%v'", url.Scheme) continue } // attempt to start the server lumber.Info("Starting '%v' server...", url.Scheme) go server(url.Host, errChan) } // handle errors that happen during startup by reading off errChan and returning // on any error received. If no errors are received after 1 second per server // assume successful starts. select { case err := <-errChan: lumber.Error("Failed to start - %v", err) return err case <-time.After(time.Second * time.Duration(len(uris))): // no errors } // handle errors that happen after initial start; if any errors are received they // are logged and the servers just try to keep running for err := range errChan { // log these errors and continue lumber.Error("Server error - %v", err) } return nil }