diff --git a/README.md b/README.md index 97ffe67..fd3a7e6 100755 --- a/README.md +++ b/README.md @@ -44,7 +44,24 @@ class Greet extends HttpApi { The request context is passed into a request value named *request* and contains a [RequestContext instance](https://github.com/xp-forge/lambda-ws#request-context). The [lambda context](https://github.com/xp-forge/lambda#context) is passed in *context*. -To run existing web applications, return an instance of your application subclass from the *routes()* method. This way, you can also test them locally using the `xp web` command. +To run existing web applications, return an instance of your `web.Application` subclass from the *routes()* method. + +Development & testing +--------------------- +To run the HTTP APIs locally, this library integrates with [xp-forge/web](https://github.com/xp-forge/web) via a wrapper: + +```bash +$ xp web com.amazon.aws.lambda.Ws Greet +@xp.web.srv.Standalone(HTTP @ peer.ServerSocket(Resource id #124 -> tcp://127.0.0.1:8080)) +Serving prod:Lambda[] > web.logging.ToConsole +════════════════════════════════════════════════════════════════════════ +> Server started: http://localhost:8080 in 0.057 seconds + Sat, 18 Nov 2023 12:19:32 +0100 - PID 18668; press Ctrl+C to exit + +# ... +``` + +By adding `-m develop`, these can be run in the development webserver. Setup and deployment -------------------- diff --git a/src/main/php/xp/lambda/Web.class.php b/src/main/php/xp/lambda/Web.class.php new file mode 100755 index 0000000..b82e9e5 --- /dev/null +++ b/src/main/php/xp/lambda/Web.class.php @@ -0,0 +1,86 @@ +arguments())) { + throw new IllegalArgumentException('Need an argument'); + } + + $this->app= XPClass::forName($arguments[0]); + parent::__construct($environment); + } + + /** @return web.Routing */ + public function routes() { + + // Runtime context + $function= strtolower($this->app->getSimpleName()); + $region= $this->environment->variable('AWS_REGION') ?? self::REGION; + $functionArn= "arn:aws:lambda:{$region}:123456789012:function:{$function}"; + $headers= [ + 'Lambda-Runtime-Aws-Request-Id' => [UUID::randomUUID()->hashCode()], + 'Lambda-Runtime-Invoked-Function-Arn' => [$functionArn], + 'Lambda-Runtime-Trace-Id' => [self::TRACE], + 'Lambda-Runtime-Deadline-Ms' => [(time() + 900) * 1000], + ]; + $context= new Context($headers, $_ENV + [ + 'AWS_LAMBDA_FUNCTION_NAME' => $function, + 'AWS_REGION' => $region, + 'AWS_LOCAL' => true, + ]); + + // See https://github.com/awsdocs/aws-lambda-developer-guide/blob/main/sample-apps/nodejs-apig/event-v2.json + $lambda= function($req, $res, $inv) use($function, $context) { + $via= new RequestContext([ + 'accountId' => '123456789012', + 'apiId' => 'x17bf9mIws', + 'domainName' => 'x17bf9mIws.execute-api.test-local-1.amazonaws.com', + 'domainPrefix' => 'x17bf9mIws', + 'requestId' => 'JKJaXmPLvHcESHA=', + 'routeKey' => "ANY /{$function}-function-1G3XMPLZXVXYI", + 'stage' => '$default', + 'timeEpoch' => time() * 1000, + 'http' => [ + 'method' => $req->method(), + 'path' => $req->uri()->path(), + 'protocol' => 'HTTP/1.1', + 'sourceIp' => $req->header('Remote-Addr'), + 'userAgent' => $req->header('User-Agent'), + ] + ]); + + // Add response headers replicating the inconsistent casing AWS uses + $res->header('x-amzn-RequestId', $context->awsRequestId); + $res->header('X-Amzn-Trace-Id', $context->traceId); + return $inv->proceed($req->pass('context', $context)->pass('request', $via), $res); + }; + + return new Filters([$lambda], $this->app + ->newInstance(new Environment($this->environment->webroot(), Console::$out)) + ->routes($this->enviroment) + ); + } + + /** @return string */ + public function toString() { + return nameof($this).'<'.$this->app->getName().'>'; + } +} \ No newline at end of file