mirror of
https://gitea.invidious.io/iv-org/shard-kemal.git
synced 2024-08-15 00:53:36 +00:00
Better file upload
This commit is contained in:
parent
72dc6cf775
commit
6fe57d5e78
4 changed files with 50 additions and 32 deletions
12
src/kemal/file_upload.cr
Normal file
12
src/kemal/file_upload.cr
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# :nodoc:
|
||||||
|
struct FileUpload
|
||||||
|
getter tmpfile : Tempfile
|
||||||
|
getter tmpfile_path : String
|
||||||
|
getter filename : String
|
||||||
|
getter meta : HTTP::FormData::FileMetadata
|
||||||
|
getter headers : HTTP::Headers
|
||||||
|
|
||||||
|
def initialize(@tmpfile, @tmpfile_path, @meta, @headers)
|
||||||
|
@filename = @meta.filename.not_nil!
|
||||||
|
end
|
||||||
|
end
|
|
@ -72,33 +72,3 @@ end
|
||||||
def gzip(status : Bool = false)
|
def gzip(status : Bool = false)
|
||||||
add_handler HTTP::DeflateHandler.new if status
|
add_handler HTTP::DeflateHandler.new if status
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
|
||||||
struct UploadFile
|
|
||||||
getter field : String
|
|
||||||
getter data : IO::Delimited
|
|
||||||
getter meta : HTTP::FormData::FileMetadata
|
|
||||||
getter headers : HTTP::Headers
|
|
||||||
|
|
||||||
def initialize(@field, @data, @meta, @headers)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses a multipart/form-data request. Yields an `UploadFile` object with `field`, `data`, `meta`, `headers` fields.
|
|
||||||
# Consider the example below taking two image uploads as image1, image2. To get the relevant data
|
|
||||||
# for each file you can use simple `if/switch` conditionals.
|
|
||||||
#
|
|
||||||
# post "/upload" do |env|
|
|
||||||
# parse_multipart(env) do |f|
|
|
||||||
# image1 = f.data if f.field == "image1"
|
|
||||||
# image2 = f.data if f.field == "image2"
|
|
||||||
# puts f.meta
|
|
||||||
# puts f.headers
|
|
||||||
# "Upload complete"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def parse_multipart(env)
|
|
||||||
HTTP::FormData.parse(env.request) do |field, data, meta, headers|
|
|
||||||
yield UploadFile.new field, data, meta, headers
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require "json"
|
require "json"
|
||||||
require "uri"
|
require "uri"
|
||||||
|
require "tempfile"
|
||||||
|
|
||||||
module Kemal
|
module Kemal
|
||||||
# ParamParser parses the request contents including query_params and body
|
# ParamParser parses the request contents including query_params and body
|
||||||
|
@ -8,14 +9,17 @@ module Kemal
|
||||||
class ParamParser
|
class ParamParser
|
||||||
URL_ENCODED_FORM = "application/x-www-form-urlencoded"
|
URL_ENCODED_FORM = "application/x-www-form-urlencoded"
|
||||||
APPLICATION_JSON = "application/json"
|
APPLICATION_JSON = "application/json"
|
||||||
|
MULTIPART_FORM = "multipart/form-data"
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
alias AllParamTypes = Nil | String | Int64 | Float64 | Bool | Hash(String, JSON::Type) | Array(JSON::Type)
|
alias AllParamTypes = Nil | String | Int64 | Float64 | Bool | Hash(String, JSON::Type) | Array(JSON::Type)
|
||||||
|
getter files
|
||||||
|
|
||||||
def initialize(@request : HTTP::Request)
|
def initialize(@request : HTTP::Request)
|
||||||
@url = {} of String => String
|
@url = {} of String => String
|
||||||
@query = HTTP::Params.new({} of String => Array(String))
|
@query = HTTP::Params.new({} of String => Array(String))
|
||||||
@body = HTTP::Params.new({} of String => Array(String))
|
@body = HTTP::Params.new({} of String => Array(String))
|
||||||
@json = {} of String => AllParamTypes
|
@json = {} of String => AllParamTypes
|
||||||
|
@files = {} of String => FileUpload
|
||||||
@url_parsed = false
|
@url_parsed = false
|
||||||
@query_parsed = false
|
@query_parsed = false
|
||||||
@body_parsed = false
|
@body_parsed = false
|
||||||
|
@ -41,8 +45,16 @@ module Kemal
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
def parse_body
|
def parse_body
|
||||||
return if (@request.headers["Content-Type"]? =~ /#{URL_ENCODED_FORM}/).nil?
|
content_type = @request.headers["Content-Type"]?
|
||||||
|
return unless content_type
|
||||||
|
if content_type.try(&.starts_with?(URL_ENCODED_FORM))
|
||||||
@body = parse_part(@request.body)
|
@body = parse_part(@request.body)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if content_type.try(&.starts_with?(MULTIPART_FORM))
|
||||||
|
parse_file_upload
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_query
|
def parse_query
|
||||||
|
@ -57,6 +69,22 @@ module Kemal
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_file_upload
|
||||||
|
HTTP::FormData.parse(@request) do |field, data, meta, headers|
|
||||||
|
next unless meta
|
||||||
|
filename = meta.filename
|
||||||
|
if !filename.nil?
|
||||||
|
tempfile = Tempfile.new(filename)
|
||||||
|
::File.open(tempfile.path, "w") do |file|
|
||||||
|
IO.copy(data, file)
|
||||||
|
end
|
||||||
|
@files[field] = FileUpload.new(tmpfile: tempfile, tmpfile_path: tempfile.path, meta: meta, headers: headers)
|
||||||
|
else
|
||||||
|
@body[field] = data.gets_to_end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Parses JSON request body if Content-Type is `application/json`.
|
# Parses JSON request body if Content-Type is `application/json`.
|
||||||
# If request body is a JSON Hash then all the params are parsed and added into `params`.
|
# If request body is a JSON Hash then all the params are parsed and added into `params`.
|
||||||
# If request body is a JSON Array it's added into `params` as `_json` and can be accessed
|
# If request body is a JSON Array it's added into `params` as `_json` and can be accessed
|
||||||
|
|
|
@ -34,6 +34,8 @@ module Kemal
|
||||||
raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_defined?
|
raise Kemal::Exceptions::RouteNotFound.new(context) unless context.route_defined?
|
||||||
route = context.route_lookup.payload.as(Route)
|
route = context.route_lookup.payload.as(Route)
|
||||||
content = route.handler.call(context)
|
content = route.handler.call(context)
|
||||||
|
ensure
|
||||||
|
remove_tmpfiles(context)
|
||||||
if Kemal.config.error_handlers.has_key?(context.response.status_code)
|
if Kemal.config.error_handlers.has_key?(context.response.status_code)
|
||||||
raise Kemal::Exceptions::CustomException.new(context)
|
raise Kemal::Exceptions::CustomException.new(context)
|
||||||
end
|
end
|
||||||
|
@ -41,6 +43,12 @@ module Kemal
|
||||||
context
|
context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private def remove_tmpfiles(context)
|
||||||
|
context.params.files.each do |field, file|
|
||||||
|
File.delete(file.tmpfile_path) if ::File.exists?(file.tmpfile_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private def radix_path(method : String, path)
|
private def radix_path(method : String, path)
|
||||||
"/#{method.downcase}#{path}"
|
"/#{method.downcase}#{path}"
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue