2 Commits

  1. 18
      examples/dirbounce.ru
  2. 27
      examples/errorpages.ru
  3. 3
      examples/uploader/files/.bash_logout
  4. 5
      examples/uploader/files/.bash_profile
  5. 10
      examples/uploader/files/.bashrc
  6. BIN
      examples/uploader/files/lab5.zip
  7. 27
      examples/uploader/form.ru
  8. 31
      examples/uploader/index.rhtml
  9. 1
      examples/uploader/lib
  10. 1
      examples/uploader/readme.txt
  11. 2
      landline.gemspec
  12. 4
      lib/landline/dsl/constructors_probe.rb
  13. 21
      lib/landline/dsl/methods_path.rb
  14. 11
      lib/landline/dsl/methods_template.rb
  15. 2
      lib/landline/node.rb
  16. 62
      lib/landline/path.rb
  17. 2
      lib/landline/pattern_matching/glob.rb
  18. 2
      lib/landline/pattern_matching/rematch.rb
  19. 6
      lib/landline/probe/handler.rb
  20. 1
      lib/landline/probe/serve_handler.rb
  21. 11
      lib/landline/template.rb
  22. 2
      lib/landline/template/erb.rb
  23. 2
      lib/landline/template/erubi.rb
  24. 2
      lib/landline/util/mime.rb
  25. 11
      lib/landline/util/multipart.rb

18
examples/dirbounce.ru

@ -0,0 +1,18 @@
# frozen_string_literal: true
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib")
require 'landline'
app = Landline::Server.new do
path "/hello" do
bounce
get "/world" do
"Hello world!"
end
end
get "/hello/user" do
"Hello user!"
end
end
run app

27
examples/errorpages.ru

@ -0,0 +1,27 @@
# frozen_string_literal: true
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib")
require 'landline'
app = Landline::Server.new do
get "/hello" do
header "content-type", "text/plain"
"Hello World!"
end
get "/error" do
raise StandardError, "I raised an error! (and that's very sad)"
end
handle do |status, backtrace: nil|
page = ([Landline::Util::HTTP_STATUS[status]] +
(backtrace || [""])).join("\n")
[
{
"content-length": page.bytesize,
"content-type": "text/plain"
},
page
]
end
end
run app

3
examples/uploader/files/.bash_logout

@ -0,0 +1,3 @@
#
# ~/.bash_logout
#

5
examples/uploader/files/.bash_profile

@ -0,0 +1,5 @@
#
# ~/.bash_profile
#
[[ -f ~/.bashrc ]] && . ~/.bashrc

10
examples/uploader/files/.bashrc

@ -0,0 +1,10 @@
#
# ~/.bashrc
#
# If not running interactively, don't do anything
[[ $- != *i* ]] && return
# alias ls='ls --color=auto'
# alias grep='grep --color=auto'
# PS1='[\u@\h \W]\$ '

BIN
examples/uploader/files/lab5.zip

27
examples/uploader/form.ru

@ -0,0 +1,27 @@
# frozen_string_literal: true
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib")
require 'landline'
app = Landline::Server.new do
root ENV["PWD"]
index ["index.html"]
post "/" do
formdata = form if form?
files = {}
if formdata
formdata["form_files"].each do |file|
filename = file.filename.split("/").last
`mv #{file.tempfile.path} $PWD/files/#{filename}`
files[file.filename] = "<a href=\"files/#{filename}\">#{filename}</a>"
end
end
erubi(file("index.rhtml"), { formdata: files }).run
end
serve "/files/*"
get "/" do
erubi(file("index.rhtml")).run
end
end
run app

31
examples/uploader/index.rhtml

@ -0,0 +1,31 @@
<!DOCTYPE>
<html>
<head>
<title>Form upload test</title>
<style>
.form { display: flex; flex-direction: column }
</style>
</head>
<body>
<h1>File uploader</h1>
<hr/>
<p> Add files here: <p>
<form method="post"
class="form"
enctype="multipart/form-data">
<input id="form_files" type="file" name="form_files[]" multiple>
<label for="form_files">Attach file</label>
<input type="submit" value="Send form">
</form>
<% if (defined? formdata) and formdata %>
<hr>
<ul>
<% formdata.each do |key, part| %>
<li><%= key %>: <%= part %></li>
<% end %>
</ul>
<% end %>
</body>
</html>

1
examples/uploader/lib

@ -0,0 +1 @@
../../lib

1
examples/uploader/readme.txt

@ -0,0 +1 @@
Example of handling forms in Landline

2
landline.gemspec

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

4
lib/landline/dsl/constructors_probe.rb

@ -5,7 +5,7 @@ module Landline
# Probe (and subclasses) DSL construct
module ProbeConstructors
# Create a new erb template
# @see {Landline::Template#new}
# @see Landline::Template#new
def erb(input, vars = {})
Landline::Templates::ERB.new(input,
vars,
@ -14,7 +14,7 @@ module Landline
end
# Create a new erb template using Erubi engine
# @see {Landline::Template#new}
# @see Landline::Template#new
# @param freeze [Boolean] whether to use frozen string literal
# @param capture [Boolean] whether to enable output capturing
def erubi(input, vars = {}, freeze: true, capture: false)

21
lib/landline/dsl/methods_path.rb

@ -5,6 +5,27 @@ module Landline
module DSL
# Common path methods
module PathMethods
# Bounce request if no handler found instead of issuing 404
def bounce
@origin.bounce = true
end
# Create a status code handler on path.
# Recursively applies to all paths unless overridden.
# @param code [Integer, nil] Specify a status code to handle
# @param block [#call] Block to run on given code
def handle(code = nil, &block)
@origin.properties["handle.#{code || 'default'}"] = block
end
# Insert a pass-through pipeline into request processing
# (i.e. for error handling purposes).
# Passed block should yield request (and return yielded data back).
# @param block [#call] block that yields request
def pipeline(&block)
@origin.pipeline = block
end
# Set path index
# @param index [Array,String]
def index(index)

11
lib/landline/dsl/methods_template.rb

@ -8,8 +8,17 @@ module Landline
# Common methods for template contexts
module TemplateMethods
# Import a template part
# @param filepath [String, File] path to the file (or the file itself)
# @return [String] compiled template
def import(filepath)
@parent_template.import(file(filepath)).run
template = if filepath.is_a? File
filepath
elsif filepath.start_with? "/"
File.open(filepath)
else
file(filepath)
end
@parent_template.import(template).run
end
end
end

2
lib/landline/node.rb

@ -35,7 +35,7 @@ module Landline
end
# Try to navigate the path. Run method callback in response.
# @param [Landline::Request]
# @param request [Landline::Request]
# @return [Boolean]
def go(request)
# rejected at pattern

62
lib/landline/path.rb

@ -5,12 +5,16 @@ 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
@ -54,21 +58,11 @@ module Landline
# @return [Boolean] true if further navigation will be done
# @raise [UncaughtThrowError] by default throws :response if no matches found.
def process(request)
return false unless run_filters(request)
run_preprocessors(request)
enqueue_postprocessors(request)
@children.each do |x|
if (value = x.go(request))
return value
end
if @pipeline
@pipeline.call(request) { |inner_req| process_wrapped(inner_req) }
else
process_wrapped(request)
end
value = index(request)
return value if value
_die(404)
rescue StandardError => e
_die(500, backtrace: [e.to_s] + e.backtrace)
end
# Add a preprocessor to the path.
@ -95,7 +89,9 @@ module Landline
@filters.append(block)
end
attr_reader :children, :properties
attr_reader :children, :properties, :request
attr_accessor :bounce, :pipeline
private
@ -123,6 +119,31 @@ module Landline
request.postprocessors.append(*@postprocessors)
end
# Method callback on successful request navigation.
# Finds the next appropriate path to go to.
# (inner pipeline-wrapped handler)
# @return [Boolean] true if further navigation will be done
# @raise [UncaughtThrowError] by default throws :response if no matches found.
def process_wrapped(request)
@request = request
return false unless run_filters(request)
run_preprocessors(request)
enqueue_postprocessors(request)
@children.each do |x|
value = x.go(request)
return value if value
end
value = index(request)
return value if value
@bounce ? false : _die(404)
rescue StandardError => e
_die(500, backtrace: [e.to_s] + e.backtrace)
ensure
@request = nil
end
# Try to perform indexing on the path if possible
# @param request [Landline::Request]
# @return [Boolean] true if indexing succeeded
@ -146,11 +167,12 @@ module Landline
# @raise [UncaughtThrowError] throws :finish to stop processing
def _die(errorcode, backtrace: nil)
throw :finish, [errorcode].append(
*(@properties["handle.#{errorcode}"] or
@properties["handle.default"]).call(
errorcode,
backtrace: backtrace
)
*@proccontext.instance_exec(
errorcode,
backtrace: backtrace,
&(@properties["handle.#{errorcode}"] or
@properties["handle.default"])
)
)
end
end

2
lib/landline/pattern_matching/glob.rb

@ -52,7 +52,7 @@ module Landline
# - underscores
# - dashes
class Glob
# @param input [String] Glob pattern
# @param pattern [String] Glob pattern
def initialize(pattern)
pattern = Landline::PatternMatching.canonicalize(pattern)
pieces = pattern.split(TOKENS)

2
lib/landline/pattern_matching/rematch.rb

@ -39,7 +39,7 @@ module Landline
end
# Test if input is convertible and if it should be converted.
# @param input
# @param string [String] input to test conversion on
# @return [Boolean] Input can be safely converted to Glob
def self.can_convert?(string)
string.is_a? Regexp

6
lib/landline/probe/handler.rb

@ -7,7 +7,7 @@ module Landline
# Probe that executes a callback on request
class Handler < Landline::Probe
# @param path [Object]
# @param parent [Landline::Node]
# @param args [Hash] hashed parameters to passthrough to Probe
# @param exec [#call]
def initialize(path, **args, &exec)
super(path, **args)
@ -15,10 +15,10 @@ module Landline
@context = Landline::ProbeContext.new(self)
@response = nil
end
attr_accessor :response
attr_reader :request
# Method callback on successful request navigation.
# Runs block supplied with object initialization.
# Request's #splat and #param are passed to block.

1
lib/landline/probe/serve_handler.rb

@ -9,7 +9,6 @@ module Landline
class Serve < Landline::Probe
# @param path [Object]
# @param parent [Landline::Node]
# @param exec [#call]
def initialize(path, parent:)
super(path, parent: parent, filepath: true)
end

11
lib/landline/template.rb

@ -86,9 +86,14 @@ module Landline
# ... (stub)
end
# Import a template from within current template
def import(filepath)
newtemp = self.class.new(filepath, {}, parent: @parent)
# Import a template from within current template.
# @param file [File] template file to import
# @return [Landline::Template] loaded tempalte
def import(file)
newtemp = self.class.new(file,
{},
parent: @parent,
filename: file.path)
newtemp.binding = @binding
newtemp
end

2
lib/landline/template/erb.rb

@ -7,7 +7,7 @@ module Landline
module Templates
# ERB Template language adapter
class ERB < Landline::Template
# @see {Landline::Template#new}
# (see Landline::Template#new)
def initialize(input, vars = nil, parent:, filename:)
super
varname = "_part_#{SecureRandom.hex(10)}".to_sym

2
lib/landline/template/erubi.rb

@ -7,7 +7,7 @@ module Landline
module Templates
# Erubi (ERB) template language adapter
class Erubi < Landline::Template
# @see {Landline::Template#new}
# (see Landline::Template#new)
def initialize(input,
vars = nil,
**ext)

2
lib/landline/util/mime.rb

@ -1267,7 +1267,7 @@ module Landline
}.freeze
# Get MIME type by file extension
# @param ext [String] filename
# @param file [String] filename
# @return [String] MIME type, defaults to "application/octet-stream"
def self.get_mime_type(file)
MIME[file.match(/\.(\w+)$/)&.[](1)] or "application/octet-stream"

11
lib/landline/util/multipart.rb

@ -9,6 +9,8 @@ require_relative 'html'
module Landline
module Util
FormPart = Struct.new(:data, :name, :filename,
:filetype, :tempfile, :headers)
# Valid element of form data with headers
# @!attribute headers [Hash] headers recevied from form data
# @!attribute name [String] name of the form part
@ -16,8 +18,7 @@ module Landline
# @!attribute filename [String,nil] Original name of the sent file
# @!attribute filetype [String,nil] MIME-type of the file
# @!attribute tempfile [File,nil] Temporary file for storing sent file data.
FormPart = Struct.new(:data, :name, :filename,
:filetype, :tempfile, :headers) do
class FormPart
# Is this form part a file or plain data?
# @return [Boolean]
def file?
@ -135,7 +136,8 @@ module Landline
end
# Setup file metadata
# @part part [FormPart]
# @param part [Landline::Util::FormPart]
# @return [void]
def setup_file_meta(part)
part.name = part.headers.dig("content-disposition", 1, "name")
part.filename = part.headers.dig("content-disposition", 1, "filename")
@ -144,7 +146,8 @@ module Landline
end
# Setup plain metadata
# @part part [FormPart]
# @param part [Landline::Util::FormPart]
# @return [void]
def setup_data_meta(part)
part.name = part.headers.dig("content-disposition", 1, "name")
end

Loading…
Cancel
Save