// ReqReplyStreamChan tests a request-reply type of com over a stream. func (*Handler) ReqReplyStreamChan(stream api.Stream, name string) error { for { var msg string err := stream.Receive(&msg) if err == io.EOF { break } if err != nil { log.Printf("Error: %v\n", err) return err } err = stream.Send(name + ":" + msg + " received") if err != nil { log.Printf("Error: %v\n", err) return err } } return stream.Close() }
// ServeHTTP serves individual HTTP requests. func (server *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) { origin := req.Header.Get("Origin") if origin != "" { // TODO: Perhaps a lever.json config param could restrict origin // to prevent CSRF. resp.Header().Set("Access-Control-Allow-Origin", origin) resp.Header().Set( "Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, "+ "X-CSRF-Token, Authorization") } if req.Method == "OPTIONS" { resp.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") resp.WriteHeader(http.StatusOK) return } if req.Method != "POST" { resp.WriteHeader(http.StatusMethodNotAllowed) return } leverURLStr := fmt.Sprintf("lever://%s%s", req.Host, req.URL.Path) leverURL, err := core.ParseLeverURL(leverURLStr) if err != nil { logger.WithFields("err", err).Debug("Error parsing Lever URL") resp.WriteHeader(http.StatusBadRequest) return } queryValues, err := url.ParseQuery(req.URL.RawQuery) if err != nil { logger.WithFields("err", err).Debug("Error parsing URL query") resp.WriteHeader(http.StatusBadRequest) return } forceEnv := queryValues.Get("forceenv") if forceEnv != "" { leverURL.Environment = forceEnv } leverURLStr = leverURL.String() reader := bufferedReaderPool.Get().(*bufio.Reader) reader.Reset(req.Body) defer bufferedReaderPool.Put(reader) defer req.Body.Close() if leverapi.IsChanMethod(leverURL.Method) { // TODO: Byte args not supported. Any way to support that? // How to delimit args from rest? done := false var line []byte line, err = reader.ReadBytes('\n') if err != nil { if err == io.EOF { done = true } else { logger.WithFields("err", err).Error("Read error") return } } if len(line) == 0 { resp.WriteHeader(http.StatusBadRequest) return } var args []interface{} err = json.Unmarshal(line, &args) if err != nil { resp.WriteHeader(http.StatusBadRequest) logger.WithFields("err", err).Debug("Malformed JSON") return } var stream leverapi.Stream stream, err = server.leverClient.InvokeChanURL(leverURLStr, args...) if err != nil { resp.WriteHeader(http.StatusInternalServerError) logger.WithFields("err", err).Error("InvokeChanURL error") return } errCh := make(chan bool) workerDoneCh := make(chan struct{}) go replyStreamWorker(stream, resp, errCh, workerDoneCh) if req.Header.Get("Content-Type") == "application/json" { for !done { line, err = reader.ReadBytes('\n') if err != nil { if err == io.EOF { done = true } else { logger.WithFields("err", err).Error("Read error") errCh <- true <-workerDoneCh return } } if len(line) > 0 { var msg interface{} err = json.Unmarshal(line, &msg) if err != nil { logger.WithFields("err", err).Debug("Malformed JSON") errCh <- true <-workerDoneCh return } stream.Send(msg) } } } else { for !done { buffer := bufferPool.Get().([]byte) defer bufferPool.Put(buffer) var size int size, err = reader.Read(buffer) if err != nil { if err == io.EOF { done = true } logger.WithFields("err", err).Error("Read error") errCh <- true <-workerDoneCh return } msg := buffer[:size] stream.Send(msg) } } err = stream.Close() if err != nil { logger.WithFields("err", err).Debug( "Stream close error") errCh <- true <-workerDoneCh return } errCh <- false <-workerDoneCh } else { buffer := bufferPool.Get().([]byte) defer bufferPool.Put(buffer) var size int size, err = io.ReadFull(reader, buffer) if err != nil { if err != io.EOF && err != io.ErrUnexpectedEOF { logger.WithFields("err", err).Error("Read error") return } } if size == maxNonChanRequestSize && err != io.EOF && err != io.ErrUnexpectedEOF { resp.WriteHeader(http.StatusBadRequest) _, err = resp.Write([]byte("\"Exceeded maximum request size\"")) if err != nil { logger.WithFields("err", err).Debug("Write error") } return } var args []interface{} contentType := req.Header.Get("Content-Type") contentTypeSplit := strings.Split(contentType, ";") switch contentTypeSplit[0] { case "application/json": err = json.Unmarshal(buffer[:size], &args) if err != nil { resp.WriteHeader(http.StatusBadRequest) logger.WithFields("err", err).Debug("JSON unmarshal error") return } case "application/x-www-form-urlencoded": // TODO resp.WriteHeader(http.StatusBadRequest) logger.WithFields("contentType", contentType).Error( "Content type not yet supported") return default: args = make([]interface{}, 1) args[0] = buffer[:size] } var reply interface{} err = server.leverClient.InvokeURL(&reply, leverURLStr, args...) if err != nil { resp.WriteHeader(http.StatusInternalServerError) remoteErr, ok := err.(*leverapi.RemoteError) if ok { reply = remoteErr.Err } else { remoteByteErr, ok := err.(*leverapi.RemoteByteError) if ok { reply = remoteByteErr.Err } else { logger.WithFields("err", err).Error("Internal Lever error") return } } } else { resp.WriteHeader(http.StatusOK) } err = writeReply(resp, reply) if err != nil { logger.WithFields("err", err).Error("Writing reply failed") return } } }
// DeployServiceChan deploys the service with provided name onto Lever. If the // service already exists, it is replaced. The method expects a code package // in the form of chunks over stream messages to be sent over. func (admin *Admin) DeployServiceChan( stream leverapi.Stream, envName string) error { // TODO: Auth layer. // Untar to a temp dir. tmpDir := tmpDir(envName) err := os.MkdirAll(tmpDir, 0777) if err != nil { logger.WithFields("err", err).Error( "Cannot create tmp dir for untar") return errInternal } success := false defer func() { if !success { remErr := os.RemoveAll(tmpDir) if remErr != nil { logger.WithFields("err", err).Error( "Error trying to remove tmp dir after failure to untar") } } }() pipeReader, pipeWriter := io.Pipe() bufReader := bufio.NewReader(pipeReader) doneCh := make(chan error, 1) go func() { untarErr := leverutil.Untar(bufReader, tmpDir) if untarErr != nil { doneCh <- untarErr } close(doneCh) }() totalSize := 0 for { var chunk []byte err = stream.Receive(&chunk) if err == io.EOF { break } if err != nil { logger.WithFields("err", err).Error("Error receiving chunks") return errInternal } var chunkSize int chunkSize, err = pipeWriter.Write(chunk) if err != nil { logger.WithFields("err", err).Error( "Error forwarding chunk to untar") return errInternal } // TODO: Also limit total size of decompressed files. totalSize += chunkSize if totalSize > maxUploadSize { logger.Error("File being uploaded is too large") return errTooLarge } // Check if there's been an untar error. select { case untarErr, hasErr := <-doneCh: if !hasErr { continue } logger.WithFields("err", untarErr).Error("Error trying to untar") return errUntar default: } } err = pipeWriter.Close() if err != nil { logger.WithFields("err", err).Error("Error closing pipe") return errInternal } // Wait for untar to finish. untarErr, hasErr := <-doneCh if hasErr && untarErr != nil { logger.WithFields("err", untarErr).Error("Error trying to untar") return errUntar } // Read lever.json. leverConfig, err := core.ReadLeverConfig(tmpDir) if err != nil { logger.WithFields("err", err).Error("Error trying to read lever.json") return errLeverJSON } // Init service table entry. createErr := store.NewService( admin.as, envName, leverConfig.Service, leverConfig.Description, !leverConfig.IsPrivate) // Ignore createErr here in case it already exists. codeVersion, err := store.NewServiceCodeVersion( admin.as, envName, leverConfig.Service) if err != nil { if createErr != nil { logger.WithFields("err", createErr).Error("Error creating service") return errInternal } logger.WithFields("err", err).Error("Error getting new service version") return errInternal } // Everything worked so far. Move the code to the right place. targetDir := codeDir(envName, leverConfig.Service, codeVersion) parentDir := filepath.Dir(targetDir) err = os.MkdirAll(parentDir, 0777) if err != nil { logger.WithFields("err", err).Error( "Cannot create target dir before move") return errInternal } err = os.Rename(tmpDir, targetDir) if err != nil { logger.WithFields("err", err).Error( "Error trying to move new service to its final destination") return errInternal } // Update entry in service table, making it point to the newly uploaded // version. err = store.UpdateService( admin.as, envName, leverConfig.Service, leverConfig.Description, !leverConfig.IsPrivate, codeVersion) if err != nil { logger.WithFields("err", err).Error("Error trying to update service") return errInternal } // TODO: Remove older versions of code to avoid having them pile up forever. success = true stream.Close() return nil }