From 1f3218f8ddcc08fb1acc441687f1630026dbd0e7 Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Thu, 29 Jun 2017 08:00:29 -0700 Subject: [PATCH] WIP: working on hot lambda functions. --- api/server/runner.go | 29 ++++++++-------- docs/writing.md | 2 ++ fn/lambda/node-4/bootstrap.js | 63 +++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/api/server/runner.go b/api/server/runner.go index c6c7e872c..04533d024 100644 --- a/api/server/runner.go +++ b/api/server/runner.go @@ -152,10 +152,10 @@ func (s *Server) loadroutes(ctx context.Context, filter models.RouteFilter) ([]* } // TODO: Should remove *gin.Context from these functions, should use only context.Context -func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, found *models.Route, app *models.App, route, reqID string, payload io.Reader, enqueue models.Enqueue) (ok bool) { - ctx, log := common.LoggerWithFields(ctx, logrus.Fields{"app": appName, "route": found.Path, "image": found.Image}) +func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, route *models.Route, app *models.App, path, reqID string, payload io.Reader, enqueue models.Enqueue) (ok bool) { + ctx, log := common.LoggerWithFields(ctx, logrus.Fields{"app": appName, "route": route.Path, "image": route.Image}) - params, match := matchRoute(found.Path, route) + params, match := matchRoute(route.Path, path) if !match { return false } @@ -165,7 +165,7 @@ func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, foun envVars := map[string]string{ "METHOD": c.Request.Method, "APP_NAME": appName, - "ROUTE": found.Path, + "ROUTE": route.Path, "REQUEST_URL": fmt.Sprintf("%v//%v%v", func() string { if c.Request.TLS == nil { return "http" @@ -173,13 +173,14 @@ func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, foun return "https" }(), c.Request.Host, c.Request.URL.String()), "CALL_ID": reqID, + "FORMAT": route.Format, } // app config for k, v := range app.Config { envVars[toEnvName("", k)] = v } - for k, v := range found.Config { + for k, v := range route.Config { envVars[toEnvName("", k)] = v } @@ -195,16 +196,16 @@ func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, foun cfg := &task.Config{ AppName: appName, - Path: found.Path, + Path: route.Path, Env: envVars, - Format: found.Format, + Format: route.Format, ID: reqID, - Image: found.Image, - Memory: found.Memory, + Image: route.Image, + Memory: route.Memory, Stdin: payload, Stdout: &stdout, - Timeout: time.Duration(found.Timeout) * time.Second, - IdleTimeout: time.Duration(found.IdleTimeout) * time.Second, + Timeout: time.Duration(route.Timeout) * time.Second, + IdleTimeout: time.Duration(route.IdleTimeout) * time.Second, ReceivedTime: time.Now(), Ready: make(chan struct{}), } @@ -223,11 +224,11 @@ func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, foun newTask.Image = &cfg.Image newTask.ID = cfg.ID newTask.CreatedAt = createdAt - newTask.Path = found.Path + newTask.Path = route.Path newTask.EnvVars = cfg.Env newTask.AppName = cfg.AppName - switch found.Type { + switch route.Type { case "async": // Read payload pl, err := ioutil.ReadAll(cfg.Stdin) @@ -263,7 +264,7 @@ func (s *Server) serve(ctx context.Context, c *gin.Context, appName string, foun break } - for k, v := range found.Headers { + for k, v := range route.Headers { c.Header(k, v[0]) } diff --git a/docs/writing.md b/docs/writing.md index 49ad8d138..a989352d3 100644 --- a/docs/writing.md +++ b/docs/writing.md @@ -35,6 +35,8 @@ You will also have access to a set of environment variables. * APP_NAME - the name of the application that matched this route, eg: `myapp` * ROUTE - the matched route, eg: `/hello` * METHOD - the HTTP method for the request, eg: `GET` or `POST` +* CALL_ID - a unique ID for each function execution. +* FORMAT - a string representing one of the [function formats](function-format.md), currently either `default` or `http`. Default is `default`. * HEADER_X - the HTTP headers that were set for this request. Replace X with the upper cased name of the header and replace dashes in the header with underscores. * X - any [configuration values](https://gitlab.oracledx.com/odx/functions/blob/master/fn/README.md#application-level-configuration) you've set for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: `minio_secret=secret` will be exposed via MINIO_SECRET env var. diff --git a/fn/lambda/node-4/bootstrap.js b/fn/lambda/node-4/bootstrap.js index e51c74643..faed0933b 100644 --- a/fn/lambda/node-4/bootstrap.js +++ b/fn/lambda/node-4/bootstrap.js @@ -1,6 +1,10 @@ 'use strict'; var fs = require('fs'); +var net = require('net'); +var http = require('http'); +var events = require('events'); +var HTTPParser = process.binding('http_parser').HTTPParser; var oldlog = console.log console.log = console.error @@ -239,8 +243,67 @@ var setEnvFromHeader = function () { } } +// for http hot functions +function freeParser(parser){ + if (parser) { + parser.onIncoming = null; + parser.socket = null; + http.parsers.free(parser); + parser = null; + } +}; + +// parses http requests +function parse(socket){ + var emitter = new events.EventEmitter(); + var parser = http.parsers.alloc(); + + parser.reinitialize(HTTPParser.REQUEST); + parser.socket = socket; + parser.maxHeaderPairs = 2000; + + parser.onIncoming = function(req){ + emitter.emit('request', req); + }; + + socket.on('data', function(buffer){ + var ret = parser.execute(buffer, 0, buffer.length); + if(ret instanceof Error){ + emitter.emit('error'); + + freeParser(parser); + } + }); + + socket.once('close', function(){ + freeParser(parser); + }); + + return emitter; +}; function run() { + + // First, check format (ie: hot functions) + var format = process.env.FORMAT; + if (format == "http"){ + // init parser + var parser = http.parsers.alloc(); + parser.reinitialize(HTTPParser.REQUEST); + parser.socket = process.stdin; + parser.maxHeaderPairs = 2000; + + parser.onIncoming = function(req){ + emitter.emit('request', req); + }; + } + + var parser = parse(socket); + + + + + setEnvFromHeader(); // FIXME(nikhil): Check for file existence and allow non-payload. var path = process.env["PAYLOAD_FILE"];