Skip to content

Commit

Permalink
Merge pull request #164 from rails-lambda/ProxyServer
Browse files Browse the repository at this point in the history
Local Development Proxy Server
  • Loading branch information
metaskills authored May 31, 2023
2 parents b53be2c + 52468e8 commit 01f6133
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 3 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

See this http://keepachangelog.com link for information on how we want this documented formatted.

## v4.2.0

### Added

- Local Development Proxy Server. See #164

## v4.1.1

### Changed
Expand Down
4 changes: 3 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
lamby (4.1.1)
lamby (4.2.0)
rack

GEM
Expand Down Expand Up @@ -170,6 +170,7 @@ GEM
timeout (0.3.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
webrick (1.8.1)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
Expand All @@ -189,6 +190,7 @@ DEPENDENCIES
pry
rails
rake
webrick

BUNDLED WITH
2.3.26
8 changes: 7 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ require "rake/testtask"
Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/*_test.rb"]
t.test_files = begin
if ENV['TEST_FILE']
[ENV['TEST_FILE']]
else
FileList["test/**/*_test.rb"]
end
end
t.verbose = false
t.warning = false
end
Expand Down
1 change: 1 addition & 0 deletions lamby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'minitest-focus'
spec.add_development_dependency 'mocha'
spec.add_development_dependency 'pry'
spec.add_development_dependency 'webrick'
end
2 changes: 2 additions & 0 deletions lib/lamby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,7 @@ def config
end

autoload :SsmParameterStore, 'lamby/ssm_parameter_store'
autoload :ProxyContext, 'lamby/proxy_context'
autoload :ProxyServer, 'lamby/proxy_server'

end
25 changes: 25 additions & 0 deletions lib/lamby/proxy_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Lamby
# This class is used by the `lamby:proxy_server` Rake task to run a
# Rack server for local development proxy. Specifically, this class
# accepts a JSON respresentation of a Lambda context object converted
# to a Hash as the single arugment.
#
class ProxyContext
def initialize(data)
@data = data
end

def method_missing(method_name, *args, &block)
key = method_name.to_s
if @data.key?(key)
@data[key]
else
super
end
end

def respond_to_missing?(method_name, include_private = false)
@data.key?(method_name.to_s) || super
end
end
end
36 changes: 36 additions & 0 deletions lib/lamby/proxy_server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module Lamby
class ProxyServer

METHOD_NOT_ALLOWED = <<-HEREDOC.strip
<h1>Method Not Allowed</h1>
<p>Please POST to this endpoint with an application/json content type and JSON payload of your Lambda's event and context.<p>
<p>Example: <code>{ "event": event, "context": context }</code></p>
HEREDOC

def call(env)
return method_not_allowed unless method_allowed?(env)
event, context = event_and_context(env)
lambda_to_rack Lamby.cmd(event: event, context: context)
end

private

def event_and_context(env)
data = env['rack.input'].dup.read
json = JSON.parse(data)
[ json['event'], Lamby::ProxyContext.new(json['context']) ]
end

def method_allowed?(env)
env['REQUEST_METHOD'] == 'POST' && env['CONTENT_TYPE'] == 'application/json'
end

def method_not_allowed
[405, {"Content-Type" => "text/html"}, [ METHOD_NOT_ALLOWED.dup ]]
end

def lambda_to_rack(response)
[ 200, {"Content-Type" => "application/json"}, [ response.to_json ] ]
end
end
end
4 changes: 4 additions & 0 deletions lib/lamby/railtie.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module Lamby
class Railtie < ::Rails::Railtie
config.lamby = Lamby::Config.config

rake_tasks do
load 'lamby/tasks.rake'
end
end
end
8 changes: 8 additions & 0 deletions lib/lamby/tasks.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace :lamby do
task :proxy_server => [:environment] do
require 'webrick'
port = ENV['LAMBY_PROXY_PORT'] || 3000
bind = ENV['LAMBY_PROXY_BIND'] || '0.0.0.0'
Rack::Handler::WEBrick.run Lamby::ProxyServer.new, Port: port, BindAddress: bind
end
end
2 changes: 1 addition & 1 deletion lib/lamby/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Lamby
VERSION = '4.1.1'
VERSION = '4.2.0'
end
29 changes: 29 additions & 0 deletions test/proxy_context_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'test_helper'

class ProxyContextTest < LambySpec

let(:context_data) { TestHelpers::LambdaContext.raw_data }
let(:proxy_context) { Lamby::ProxyContext.new(context_data) }

it 'should respond to all context methods' do
context_data.keys.each do |key|
response = proxy_context.respond_to?(key.to_sym)
expect(response).must_equal true, "Expected context to respond to #{key.inspect}"
end
end

it 'should return the correct value for each context method' do
expect(proxy_context.clock_diff).must_equal 1681486457423
expect(proxy_context.deadline_ms).must_equal 1681492072985
expect(proxy_context.aws_request_id).must_equal "d6f5961b-5034-4db5-b3a9-fa378133b0f0"
end

it 'should raise an error for unknown methods' do
expect { proxy_context.foo }.must_raise NoMethodError
end

it 'should return false for respond_to? for a unknown method' do
expect(proxy_context.respond_to?(:foo)).must_equal false
end

end
52 changes: 52 additions & 0 deletions test/proxy_server_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'net/http'
require 'test_helper'

class ProxyServerTest < LambySpec
include Rack::Test::Methods

let(:event) { TestHelpers::Events::HttpV2.create }
let(:context) { TestHelpers::LambdaContext.raw_data }
let(:app) { Rack::Builder.new { run Lamby::ProxyServer.new }.to_app }
let(:json) { {"event": event, "context": context}.to_json }

it 'should return a 405 helpful message on GET' do
response = get '/'
expect(response.status).must_equal 405
expect(response.headers).must_equal({"Content-Type" => "text/html"})
expect(response.body).must_include 'Method Not Allowed'
end

it 'should call Lamby.cmd on POST and include full response as JSON' do
response = post '/', json, 'CONTENT_TYPE' => 'application/json'
expect(response.status).must_equal 200
expect(response.headers).must_equal({"Content-Type" => "application/json"})
response_body = JSON.parse(response.body)
expect(response_body['statusCode']).must_equal 200
expect(response_body['headers']).must_be_kind_of Hash
expect(response_body['body']).must_match 'Hello Lamby'
end

it 'will return whatever Lamby.cmd does' do
Lamby.stubs(:cmd).returns({statusCode: 200})
response = post '/', json, 'CONTENT_TYPE' => 'application/json'
expect(response.status).must_equal 200
expect(response.headers).must_equal({"Content-Type" => "application/json"})
response_body = JSON.parse(response.body)
expect(response_body['statusCode']).must_equal 200
expect(response_body['headers']).must_be_nil
expect(response_body['body']).must_be_nil
end

it 'will use the configured Lamby rack_app' do
rack_app = Rack::Builder.new { run lambda { |env| [200, {}, StringIO.new('OK')] } }.to_app
Lamby.config.rack_app = rack_app
response = post '/', json, 'CONTENT_TYPE' => 'application/json'
expect(response.status).must_equal 200
expect(response.headers).must_equal({"Content-Type" => "application/json"})
response_body = JSON.parse(response.body)
expect(response_body['statusCode']).must_equal 200
expect(response_body['headers']).must_equal({})
expect(response_body['body']).must_equal('OK')
end

end
16 changes: 16 additions & 0 deletions test/test_helper/lambda_context.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
module TestHelpers
class LambdaContext

RAW_DATA ={
"clock_diff" => 1681486457423,
"deadline_ms" => 1681492072985,
"aws_request_id" => "d6f5961b-5034-4db5-b3a9-fa378133b0f0",
"invoked_function_arn" => "arn:aws:lambda:us-east-1:576043675419:function:lamby-ws-production-WSConnectLambda-5in18cNskwz6",
"log_group_name" => "/aws/lambda/lamby-ws-production-WSConnectLambda-5in18cNskwz6",
"log_stream_name" => "2023/04/14/[$LATEST]55a1d458479a4546b64acca17af3a69f",
"function_name" => "lamby-ws-production-WSConnectLambda-5in18cNskwz6",
"memory_limit_in_mb" => "1792",
"function_version" => "$LATEST"
}.freeze

def self.raw_data
RAW_DATA.dup
end

def clock_diff
1585237646907
end
Expand Down

0 comments on commit 01f6133

Please sign in to comment.