Mercurial Hosting > luan
view src/luan/modules/http/Http.luan @ 2008:bba3e529e346 default tip
chunked encoding
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 27 Aug 2025 01:14:17 -0600 |
parents | 31f006c64782 |
children |
line wrap: on
line source
require "java" local Luan = require "luan:Luan.luan" local error = Luan.error local ipairs = Luan.ipairs or error() local pairs = Luan.pairs or error() local type = Luan.type or error() local set_metatable = Luan.set_metatable or error() local get_local_cloned = Luan.get_local_cloned or error() local set_local_cloned = Luan.set_local_cloned or error() local get_local_only = Luan.get_local_only or error() local set_local_only = Luan.set_local_only or error() local raw_set = Luan.raw_set or error() local Io = require "luan:Io.luan" local Html = require "luan:Html.luan" local Table = require "luan:Table.luan" local clear = Table.clear or error() local java_to_table_deep = Table.java_to_table_deep or error() local case_insensitive = Table.case_insensitive or error() local Package = require "luan:Package.luan" local String = require "luan:String.luan" local lower = String.lower or error() local trim = String.trim or error() local regex = String.regex or error() local Time = require "luan:Time.luan" local format_time = Time.format or error() local parse_time = Time.parse or error() local Boot = require "luan:Boot.luan" local Thread = require "luan:Thread.luan" local LuanJava = require "java:luan.Luan" local Request = require "java:goodjava.webserver.Request" local Response = require "java:goodjava.webserver.Response" local ResponseOutputStream = require "java:goodjava.webserver.ResponseOutputStream" local ChunkedOutputStream = require "java:goodjava.webserver.ChunkedOutputStream" local Status = require "java:goodjava.webserver.Status" local ServerSentEvents = require "java:goodjava.webserver.ServerSentEvents" local OutputStreamWriter = require "java:java.io.OutputStreamWriter" local HashMap = require "java:java.util.HashMap" local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "Http" local Http = {} local mt = {} function mt.__index(tbl,key) if key=="error_priority" or key=="not_found_handler" then return get_local_cloned(tbl,key) elseif key=="request" or key=="response" then return get_local_only(tbl,key) else return nil end end function mt.__new_index(tbl,key,value) if key=="error_priority" or key=="not_found_handler" then set_local_cloned(tbl,key,value) elseif key=="request" or key=="response" then set_local_only(tbl,key,value) else raw_set(tbl,key,value) end end set_metatable(Http,mt) local old_java_to_table_shallow = Table.java_to_table_shallow or error() local function java_to_table_shallow(obj) if type(obj)=="java" and obj.instanceof(Request.MultipartFile) then return { filename = obj.filename content_type = obj.contentType content = obj.content } end return old_java_to_table_shallow(obj) end function Http.new_request(java) local request = {} Http.request = request if java == nil then request.method = "GET" request.scheme = "http" request.headers = case_insensitive{} request.parameters = {} request.cookies = {} else request.java = java request.raw_head = java.rawHead or error() request.body = java.body request.method = java.method or error() request.raw_path = java.rawPath or error() request.original_path = java.originalPath or error() request.path = java.path or error() request.protocol = java.protocol or error() request.scheme = java.scheme or error() request.headers = case_insensitive(java_to_table_deep(java.headers)) request.parameters = java_to_table_deep(java.parameters,java_to_table_shallow) request.cookies = java_to_table_deep(java.cookies) end function request.url() return request.scheme.."://"..request.headers["Host"]..request.raw_path end function request.set_path(path) request.path = path java.path = path end return request end local STATUS = { OK = 200 MOVED_PERMANENTLY = 301 FOUND = 302 NOT_FOUND = 404 -- add more as needed } Http.STATUS = STATUS function Http.new_response() local response = {} Http.response = response function response.reset() response.java = Response.new() response.headers = case_insensitive{} response.status = STATUS.OK response.writer = nil end response.reset() function response.send_redirect(location) response.status = STATUS.FOUND response.headers["Location"] = location end function response.send_error(status,msg) response.reset() response.status = status if msg ~= nil then response.headers["Content-Type"] = "text/plain; charset=utf-8" local writer = response.text_writer() writer.write(msg) end end function response.set_cookie(name,value,attributes) attributes = attributes or {} attributes["Path"] = attributes["Path"] or "/" local attrMap = HashMap.new() for attr_name, attr_value in pairs(attributes) do type(attr_name)=="string" or "cookie attribute name must be string" type(attr_value)=="string" or "cookie attribute value must be string" attrMap.put(attr_name,attr_value) end response.java.setCookie(name,value,attrMap) end function response.set_persistent_cookie(name,value,attributes) attributes = attributes or {} attributes["Max-Age"] = "10000000" response.set_cookie(name,value,attributes) end function response.remove_cookie(name,attributes) attributes = attributes or {} attributes["Max-Age"] = "0" response.set_cookie(name,"delete",attributes) end function response.text_writer() response.writer and error "writer already set" response.writer = ResponseOutputStream.new(response.java) response.writer = OutputStreamWriter.new(response.writer) return Boot.text_writer(response.writer) end function response.binary_writer() response.writer and error "writer already set" response.writer = ResponseOutputStream.new(response.java) return Boot.binary_writer(response.writer) end function response.write_chunked_text(fn) response.writer and error "writer already set" response.writer = "done" local writer = ChunkedOutputStream.new(response.java) writer = OutputStreamWriter.new(writer) writer = Boot.text_writer(writer) Thread.run(function() try fn(writer) finally writer.close() end end) end function response.write_chunked_binary(fn) response.writer and error "writer already set" response.writer = "done" local writer = ChunkedOutputStream.new(response.java) writer = Boot.binary_writer(writer) Thread.run(function() try fn(writer) finally writer.close() end end) end return response end function Http.finish() -- called only from java local response = Http.response or error() local java = response.java or error() java.status = Status.getStatus(response.status) for name, value in pairs(response.headers) do type(name)=="string" or "header name must be string" value = LuanJava.toJava(value) java.headers.put(name,value) end response.writer and response.writer~="done" and response.writer.close() return java end function Http.error_priority(e) return "error" end function Http.handle_error(java_request,e) Http.new_request(java_request) local call = Http.error_priority(e) local err = e.get_stack_trace_string() logger[call](err.."\n"..trim(java_request.rawHead).."\n") local msg = "Internal Server Error\n\n"..err return Response.errorResponse( Status.INTERNAL_SERVER_ERROR, msg ) end Http.domain = nil -- set in domain specific cases local domain_regex = nil function Http.set_domain(domain) Http.domain = domain or error() domain_regex = regex( [[^https?://]]..domain..[[(/|:)]] ) end Http.is_serving = false local date_format = "EEE, dd MMM yyyy HH:mm:ss z" Http.date_format = date_format function Http.format_date(date) return format_time(date,date_format,"GMT") end function Http.parse_date(source) return parse_time(date_format,source,"GMT") end local sse_push = ServerSentEvents.writeMessage function Http.push( url, message ) domain_regex==nil or domain_regex.matches(url) or error "can't push to another domain" sse_push(url,message) end return Http