lib/functions_framework.rb (56 lines of code) (raw):
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require "logger"
require "cloud_events"
require "functions_framework/function"
require "functions_framework/legacy_event_converter"
require "functions_framework/registry"
require "functions_framework/version"
##
# The Functions Framework for Ruby.
#
# Functions Framework is an open source framework for writing lightweight,
# portable Ruby functions that run in a serverless environment. For general
# information about the Functions Framework, see
# https://github.com/GoogleCloudPlatform/functions-framework.
# To get started with the functions framework for Ruby, see
# https://github.com/GoogleCloudPlatform/functions-framework-ruby for basic
# examples.
#
# ## Inside the FunctionsFramework module
#
# The FunctionsFramework module includes the main entry points for the
# functions framework. Use the {FunctionsFramework.http},
# {FunctionsFramework.event}, or {FunctionsFramework.cloud_event} methods to
# define functions. To serve functions via a web service, invoke the
# `functions-framework-ruby` executable, or use the {FunctionsFramework.start}
# or {FunctionsFramework.run} methods.
#
# ## Internal modules
#
# Here is a roadmap to the internal modules in the Ruby functions framework.
#
# * {FunctionsFramework::CLI} is the implementation of the
# `functions-framework-ruby` executable. Most apps will not need to interact
# with this class directly.
# * {FunctionsFramework::Function} is the internal representation of a
# function, indicating the type of function (http or cloud event), the
# name of the function, and the block of code implementing it. Most apps
# do not need to interact with this class directly.
# * {FunctionsFramework::Registry} looks up functions by name. When you
# define a set of named functions, they are added to a registry, and when
# you start a server and specify the target function by name, it is looked
# up from the registry. Most apps do not need to interact with this class
# directly.
# * {FunctionsFramework::Server} is a web server that makes a function
# available via HTTP. It wraps the Puma web server and runs a specific
# {FunctionsFramework::Function}. Many apps can simply run the
# `functions-framework-ruby` executable to spin up a server. However, if you
# need closer control over your execution environment, you can use the
# {FunctionsFramework::Server} class to run a server. Note that, in most
# cases, it is easier to use the {FunctionsFramework.start} or
# {FunctionsFramework.run} wrapper methods rather than instantiate a
# {FunctionsFramework::Server} class directly.
# * {FunctionsFramework::Testing} provides helpers that are useful when
# writing unit tests for functions.
#
module FunctionsFramework
@global_registry = Registry.new
@logger = ::Logger.new $stderr
@logger.level = ::Logger::INFO
##
# The default target function name. If you define a function without
# specifying a name, or run the framework without giving a target, this name
# is used.
#
# @return [String]
#
DEFAULT_TARGET = "function".freeze
##
# The default source file path. The CLI loads functions from this file if no
# source file is given explicitly.
#
# @return [String]
#
DEFAULT_SOURCE = "./app.rb".freeze
##
# The CloudEvents implementation was extracted to become the official
# CloudEvents SDK. This alias is left here for backward compatibility.
#
CloudEvents = ::CloudEvents
class << self
##
# The "global" registry that holds events defined by the
# {FunctionsFramework} class methods.
#
# @return [FunctionsFramework::Registry]
#
attr_accessor :global_registry
##
# A "global" logger that is used by the framework's web server, and can
# also be used by functions.
#
# @return [Logger]
#
attr_accessor :logger
##
# Define a function that responds to HTTP requests.
#
# You must provide a name for the function, and a block that implements the
# function. The block should take a single `Rack::Request` argument. It
# should return one of the following:
# * A standard 3-element Rack response array. See
# https://github.com/rack/rack/blob/main/SPEC.rdoc
# * A `Rack::Response` object.
# * A simple String that will be sent as the response body.
# * A Hash object that will be encoded as JSON and sent as the response
# body.
#
# ## Example
#
# FunctionsFramework.http "my-function" do |request|
# "I received a request for #{request.url}"
# end
#
# @param name [String] The function name. Defaults to {DEFAULT_TARGET}.
# @param block [Proc] The function code as a proc.
# @return [self]
#
def http name = DEFAULT_TARGET, &block
global_registry.add_http name, &block
self
end
## Define a Typed function that responds to HTTP requests.
#
# You must provide a name for the function, and a block that implements the
# function. The block should take a single argument representing the request
# payload. If a `request_type` is provided, the argument object will be of
# the given decoded type; otherwise, it will be a JSON hash. The block
# should return a JSON hash or an object that implements `#to_json`.
#
# ## Example
# FunctionsFramework.typed "my-sum-function" do |add_request|
# {sum: add_request["num1"] + add_response["num2"]}
# end
#
# ## Example with Type
# FunctionsFramework.typed "identity",
# request_class: MyCustomType do |custom_type|
# custom_type
# end
#
# @param name [String] The function name. Defaults to {DEFAULT_TARGET}
# @param request_class [#decode_json] An optional class which will be used to
# decode the request if it implements a `decode_json` static method.
# @param block [Proc] The function code as a proc @return [self]
# @return [self]
#
def typed name = DEFAULT_TARGET, request_class: nil, &block
global_registry.add_typed name, request_class: request_class, &block
self
end
##
# Define a function that responds to CloudEvents.
#
# You must provide a name for the function, and a block that implements the
# function. The block should take one argument: the event object of type
# [`CloudEvents::Event`](https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event).
# Any return value is ignored.
#
# ## Example
#
# FunctionsFramework.cloud_event "my-function" do |event|
# FunctionsFramework.logger.info "Event data: #{event.data.inspect}"
# end
#
# @param name [String] The function name. Defaults to {DEFAULT_TARGET}.
# @param block [Proc] The function code as a proc.
# @return [self]
#
def cloud_event name = DEFAULT_TARGET, &block
global_registry.add_cloud_event name, &block
self
end
##
# Define a server startup task. This is useful for initializing shared
# resources that should be accessible across all function invocations in
# this Ruby VM.
#
# Startup tasks are run just before a server starts. All startup tasks are
# guaranteed to complete before any function executes. However, they are
# run only when preparing to run functions. They are not run, for example,
# if an app is loaded to verify its integrity during deployment.
#
# Startup tasks are passed the {FunctionsFramework::Function} identifying
# the function to execute, and have no return value.
#
# @param block [Proc] The startup task
# @return [self]
#
def on_startup &block
global_registry.add_startup_task(&block)
self
end
##
# Run startup tasks, then start the functions framework server in the
# background. The startup tasks and target function will be looked up in
# the global registry.
#
# @param target [FunctionsFramework::Function,String] The function to run,
# or the name of the function to look up in the global registry.
# @yield [FunctionsFramework::Server::Config] A config object that can be
# manipulated to configure the server.
# @return [FunctionsFramework::Server]
#
def start target, &block
require "functions_framework/server"
if target.is_a? ::FunctionsFramework::Function
function = target
else
function = global_registry[target]
raise ::ArgumentError, "Undefined function: #{target.inspect}" if function.nil?
end
globals = function.populate_globals
server = Server.new function, globals, &block
global_registry.startup_tasks.each do |task|
task.call function, globals: globals, logger: server.config.logger
end
globals.freeze
server.respond_to_signals
server.start
end
##
# Run the functions framework server and block until it stops. The server
# will look up the given target function name in the global registry.
#
# @param target [FunctionsFramework::Function,String] The function to run,
# or the name of the function to look up in the global registry.
# @yield [FunctionsFramework::Server::Config] A config object that can be
# manipulated to configure the server.
# @return [self]
#
def run target, &block
server = start target, &block
server.wait_until_stopped
self
end
end
end