Example #1
0
func streamReceiveWorker(
	stream leverapi.Stream, receiveCh chan interface{},
	receiveErrCh chan error) {
	for {
		var msg interface{}
		err := stream.Receive(&msg)
		if err != nil {
			receiveErrCh <- err
			return
		}
		receiveCh <- msg
	}
}
Example #2
0
// 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()
}
Example #3
0
// 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
		}
	}
}
Example #4
0
// 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
}