Version bump and, finally, thread safety and shared request context memory

This commit is contained in:
Yessiest 2024-05-07 19:07:34 +04:00
parent de59eea736
commit e1e2154677
13 changed files with 174 additions and 124 deletions

View File

@ -19,31 +19,28 @@ end
# Example Landline application as rack middleware
class HelloServer < Landline::App
setup do
get "/test2" do
"Hello world from #{self}!"
end
handle do |status, backtrace: nil|
page = ([Landline::Util::HTTP_STATUS[status]] +
(backtrace || [""])).join("\n")
[
{
"content-length": page.bytesize,
"content-type": "text/plain",
"x-cascade": true
},
page
]
end
get "/test2" do
"Hello world from #{self}!"
end
handle do |status, backtrace: nil|
page = ([Landline::Util::HTTP_STATUS[status]] +
(backtrace || [""])).join("\n")
[
{
"content-length": page.bytesize,
"content-type": "text/plain",
"x-cascade": true
},
page
]
end
end
# Example Landline app as rack application
class CrossCallServer < Landline::App
setup do
get "/inner_test" do
"Hello world, through crosscall!"
end
get "/inner_test" do
"Hello world, through crosscall!"
end
end
@ -52,21 +49,19 @@ class Server < Landline::App
use TimerMiddleware
use HelloServer
setup do
crosscall_server = CrossCallServer.new
crosscall_server = CrossCallServer.new
get "/test" do
"Hello from #{self}!"
end
get "/test" do
"Hello from #{self}!"
end
# Cross-callable application included as a subpath
link "/outer", crosscall_server
# Cross-callable application included as a subpath
link "/outer", crosscall_server
# Cross calling an application in a probe context
get "/crosscall" do
request.path = "/inner_test"
call(crosscall_server)
end
# Cross calling an application in a probe context
get "/crosscall" do
request.path = "/inner_test"
call(crosscall_server)
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'landline'
class Application < Landline::App
def initialize(*args)
puts self
super(*args)
end
preprocess do
puts Thread.current.inspect
end
get "/long" do
before = request
sleep 10
"Request didn't change mid-execution: #{before == request}"
end
get "/long2" do
puts self
@instance_property = (rand * 1000).floor
response = "Your magic number is #{@instance_property}\n"
sleep 20
response += "Your magic number after waiting 20 seconds is #{@instance_property}"
response
end
get "/magic" do
@instance_property = (rand * 1000).floor
end
postprocess do
puts "Last value of instance_property: #{@instance_property}"
end
end
run Application.new

View File

@ -4,28 +4,26 @@ require 'landline'
require 'landline/extensions/websocket'
class Test < Landline::App
setup do
websocket "/test", version: 7 do |socket|
socket.on :message do |msg|
puts "Client wrote: #{msg}"
end
socket.on :error do |err|
puts "Error occured: #{err.inspect}"
puts err.backtrace
end
socket.on :close do
puts "Client closed read connection"
end
socket.ready
socket.write("Hi!")
response = socket.read
socket.write("You said: #{response}")
socket.write("Goodbye!")
socket.close
rescue Exception => e
puts e.inspect
puts e.backtrace
websocket "/test", version: 7 do |socket|
socket.on :message do |msg|
puts "Client wrote: #{msg}"
end
socket.on :error do |err|
puts "Error occured: #{err.inspect}"
puts err.backtrace
end
socket.on :close do
puts "Client closed read connection"
end
socket.ready
socket.write("Hi!")
response = socket.read
socket.write("You said: #{response}")
socket.write("Goodbye!")
socket.close
rescue Exception => e
puts e.inspect
puts e.backtrace
end
end

View File

@ -2,7 +2,7 @@
Gem::Specification.new do |spec|
spec.name = "landline"
spec.version = "0.12.0"
spec.version = "0.12.1"
spec.summary = "Elegant HTTP DSL"
spec.description = <<~DESC
Landline is a no-hard-dependencies HTTP routing DSL that was made entirely for fun.

View File

@ -12,7 +12,7 @@ require_relative 'landline/app'
# Landline is a backend framework born as a by-product of experimentation
module Landline
# Landline version
VERSION = '0.12 "Concrete and Gold" (pre-alpha)'
VERSION = '0.12.1 "Concrete and Gold" (pre-alpha)'
# Landline branding and version
VLINE = "Landline/#{Landline::VERSION} (Ruby/#{RUBY_VERSION}/#{RUBY_RELEASE_DATE})\n".freeze

View File

@ -3,18 +3,20 @@
module Landline
# Rack application interface
class App
# TODO: fix this mess somehow (probably impossible)
# @!parse include Landline::DSL::PathMethods
# @!parse include Landline::DSL::PathConstructors
# @!parse include Landline::DSL::ProbeConstructors
# @!parse include Landline::DSL::ProbeMethods
# @!parse include Landline::DSL::CommonMethods
class << self
# TODO: fix this mess somehow (probably impossible)
# @!parse include Landline::DSL::PathMethods
# @!parse include Landline::DSL::PathConstructors
# @!parse include Landline::DSL::ProbeConstructors
# @!parse include Landline::DSL::ProbeMethods
# @!parse include Landline::DSL::CommonMethods
# Duplicate used middleware for the subclassed app
def inherited(subclass)
super(subclass)
@setup_chain ||= []
subclass.middleware = @middleware.dup
subclass.setup_chain = @setup_chain.dup
end
# Include a middleware in application
@ -24,17 +26,30 @@ module Landline
@middleware.append(middleware)
end
# Setup block
# @param block [#call]
def setup(&block)
@setup_block = block
# Check if Server can respond to given symbol
def respond_to_missing?(symbol, _include_private)
Landline::ServerContext.instance_methods.include?(symbol) || super
end
attr_accessor :middleware, :setup_block
# Store applied app manipulations
def method_missing(symbol, *args, **params, &callback)
if Landline::ServerContext.instance_methods.include? symbol
@setup_chain.append([symbol, args, params, callback])
else
super(symbol, *args, **params, &callback)
end
end
attr_accessor :middleware, :setup_chain
end
def initialize(*args, **opts)
@app = ::Landline::Server.new(*args, **opts, &self.class.setup_block)
setup_chain = self.class.setup_chain
@app = ::Landline::Server.new(*args, **opts) do
setup_chain.each do |symbol, cargs, cparams, callback|
send(symbol, *cargs, **cparams, &callback)
end
end
self.class.middleware&.reverse_each do |cls|
@app = cls.new(@app)
end

View File

@ -4,23 +4,9 @@ require_relative 'pattern_matching'
require_relative 'node'
require_relative 'dsl/constructors_path'
require_relative 'dsl/methods_path'
require_relative 'dsl/methods_common'
require_relative 'dsl/methods_probe'
require_relative 'dsl/constructors_probe'
require_relative 'util/lookup'
module Landline
# Execution context for filters and preprocessors.
class ProcessorContext
include Landline::DSL::CommonMethods
include Landline::DSL::ProbeMethods
include Landline::DSL::ProbeConstructors
def initialize(path)
@origin = path
end
end
# Execution context for path setup block.
class PathContext
include Landline::DSL::PathConstructors
@ -31,21 +17,8 @@ module Landline
end
end
# Ephemeral proxy class to which callback execution binds
class PathExecutionOrigin
def initialize(request, properties)
@request = request
@properties = Landline::Util::LookupROProxy.new(properties)
end
attr_accessor :response
attr_reader :request, :properties
end
# Primary building block of request navigation.
class Path < Landline::Node
ExecutionOrigin = Landline::PathExecutionOrigin
ProcContext = Landline::ProcessorContext
Context = Landline::PathContext
# @param path [Object] Object to generate {Landline::Pattern} from
@ -108,8 +81,8 @@ module Landline
# Create an execution context for in-path processing blocks
def get_context(request)
exec_origin = self.class::ExecutionOrigin.new(request, @properties)
self.class::ProcContext.new(exec_origin)
request.context.origin.properties.lookup = @properties
request.context
end
# Sequentially run through all filters and drop request if one is false

View File

@ -24,28 +24,6 @@ module Landline
autoload :Link, "landline/probe/crosscall_handler"
end
# Context that provides execution context for Probes.
class ProbeContext
include Landline::DSL::ProbeConstructors
include Landline::DSL::ProbeMethods
include Landline::DSL::CommonMethods
def initialize(origin)
@origin = origin
end
end
# Ephemeral proxy class to which callback execution binds
class ProbeExecutionOrigin
def initialize(request, properties)
@request = request
@properties = Landline::Util::LookupROProxy.new(properties)
end
attr_accessor :response
attr_reader :request, :properties
end
# Test probe. Also base for all "reactive" nodes.
class Probe < Landline::Node
# @param path [Object]

View File

@ -29,8 +29,8 @@ module Landline
# @return [Boolean] true if further navigation is possible
# @raise [UncaughtThrowError] may raise if die() is called.
def process(request)
origin = Landline::ProbeExecutionOrigin.new(request, @properties)
context = Landline::ProbeContext.new(origin)
origin, context = get_context(request)
return reject(request) unless request.path.match?(/^\/?$/)
response = catch(:break) do
@ -47,6 +47,14 @@ module Landline
end
throw :finish, response
end
private
# Create a context to run handler in
def get_context(request)
request.context.origin.properties.lookup = @properties
[request.context.origin, request.context]
end
end
end
end

View File

@ -3,6 +3,7 @@
require 'uri'
require_relative 'util/query'
require_relative 'util/cookie'
require_relative 'sandbox'
module Landline
# Request wrapper for Rack protocol
@ -30,13 +31,16 @@ module Landline
@states = []
# Postprocessors for current request
@postprocessors = []
# Execution context
@context = init_context
end
# Run postprocessors
# @param response [Landline::Response]
def run_postprocessors(response)
@context.origin.properties.lookup = {}
@postprocessors.reverse_each do |postproc|
postproc.call(self, response)
@context.instance_exec(self, response, &postproc)
end
@postprocessors = []
end
@ -90,11 +94,17 @@ module Landline
attr_reader :request_method, :script_name, :path_info, :server_name,
:server_port, :server_protocol, :headers, :param, :splat,
:postprocessors, :cookies, :rack
:postprocessors, :cookies, :rack, :context
attr_accessor :path, :filepath, :query
private
# Initialize execution context
def init_context
origin = Landline::ProcessorOrigin.new(self, {})
Landline::ProcessorContext.new(origin)
end
# Initialize basic rack request parameters
# @param env [Hash]
def init_request_params(env)

32
lib/landline/sandbox.rb Normal file
View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
require_relative 'dsl/methods_common'
require_relative 'dsl/methods_probe'
require_relative 'dsl/constructors_probe'
require_relative 'util/lookup'
module Landline
# Execution context for filters and preprocessors.
class ProcessorContext
include Landline::DSL::CommonMethods
include Landline::DSL::ProbeMethods
include Landline::DSL::ProbeConstructors
def initialize(path)
@origin = path
end
attr_reader :origin
end
# Ephemeral proxy class to which callback execution binds
class ProcessorOrigin
def initialize(request, properties)
@request = request
@properties = Landline::Util::LookupROProxy.new(properties)
end
attr_accessor :response
attr_reader :request, :properties
end
end

View File

@ -14,7 +14,7 @@ module Landline
# @param parent [Landline::Node, nil] Parent object to inherit properties to
# @param setup [#call] Setup block
def initialize(passthrough = nil, parent: nil, **opts, &setup)
super("", parent: nil, **opts, &setup)
super("", parent: parent, **opts, &setup)
return if parent
@passthrough = passthrough

View File

@ -46,6 +46,8 @@ module Landline
def [](key)
@lookup.[](key)
end
attr_accessor :lookup
end
end
end