Sfoglia il codice sorgente

Performance optimization for the form handling example in Lua

Optimization of example for question ("issue") #70:

Concatenating many strings in a loop in Lua is not efficient.
The standard solution for this is a Lua string builder pattern:
Store all strings in a table and use table.concat
bel 10 anni fa
parent
commit
a1d9858c15
1 ha cambiato i file con 114 aggiunte e 64 eliminazioni
  1. 114 64
      examples/handle_form_data.lua

+ 114 - 64
examples/handle_form_data.lua

@@ -1,64 +1,114 @@
-mg.write("HTTP/1.0 200 OK\r\n")
-mg.write("Connection: close\r\n")
-mg.write("Content-Type: text/plain; charset=utf-8\r\n")
-mg.write("Cache-Control: max-age=0, must-revalidate\r\n")
-mg.write("\r\n")
-
--- Read the entire body data (POST content) into "bdata" variable.
-bdata = ""
-repeat
-  local add_data = mg.read()
-  if add_data then
-    bdata = bdata .. add_data
-  end
-until (add_data == nil);
-
--- Get the boundary string.
-bs = "--" .. ((mg.request_info.content_type):upper():match("BOUNDARY=(.*)"));
-
--- The POST data has to start with the boundary string.
--- Check this and remove the starting boundary.
-if bdata:sub(1, #bs) ~= bs then
-  error "invalid format of POST data"
-end
-bdata = bdata:sub(#bs)
-
--- The boundary now starts with CR LF.
-bs = "\r\n" .. bs
-
--- Now loop through all the parts
-while #bdata>4 do
-   -- Find the header of new part.
-   part_header_end = bdata:find("\r\n\r\n", 1, true)
-
-   -- Parse the header.
-   h = bdata:sub(1, part_header_end+2)
-   for key,val in h:gmatch("([^%:\r\n]*)%s*%:%s*([^\r\n]*)\r\n") do
-      if key:upper() == "CONTENT-DISPOSITION" then
-          form_field_name = val:match('name=%"([^%"]*)%"')
-          file_name = val:match('filename=%"([^%"]*)%"')
-      end
-   end
-
-   -- Remove the header from "bdata".
-   bdata = bdata:sub(part_header_end+4)
-
-   -- Find the end of the body by locating the boundary string.
-   part_body_end = bdata:find(bs, 1, true)
-
-   -- Isolate the content, and drop it from "bdata".
-   form_field_value = bdata:sub(1,part_body_end-1)
-   bdata = bdata:sub(part_body_end+#bs)
-
-   -- Now the data (file content or field value) is isolated: We know form_field_name and form_field_value.
-   -- Here the script should do something useful with the data. This example just sends it back to the client.
-   mg.write("Field name: " .. form_field_name .. "\r\n")
-   local len = #form_field_value
-   if len<50 then
-     mg.write("Field value: " .. form_field_value .. "\r\n")
-   else
-     mg.write("Field value: " .. form_field_value:sub(1, 40) .. " .. (" .. len .. " bytes)\r\n")
-   end
-   mg.write("\r\n")
-
-end
+function add_data(data)
+  local additional_data = mg.read()
+  if additional_data then
+    return data .. additional_data, true
+  end
+  return data, false
+end
+
+function parse_multipart_form(got_field)
+
+    local data, ok = add_data("")
+    if not ok then
+      return "Can not read data"
+    end
+
+    --[[
+    local b = mg.request_info.content_type:upper():find("BOUNDARY=");
+    if b then
+      b = mg.request_info.content_type:sub(b+9)
+    end
+    if not b or #b<16 or #b>1024 then
+      return false, "Boundary string not reasonable"
+    end
+    ]]
+    local b = "--" .. mg.request_info.content_type:upper():match("BOUNDARY=(.*)");
+    
+    --b = "--" .. b
+    if data:sub(1, #b) ~= b then
+      return false, "Multipart body does not start properly"
+    end
+    data = data:sub(#b)
+    b = "\r\n" .. b
+
+    -- while there are unread parts
+    while #data>0 and data~="--\r\n" do
+
+      local name = nil
+      local value = nil
+      local file_name = nil
+      local file_type = nil
+
+      -- isolate the header of the new part
+      local part_header_end
+      repeat
+        part_header_end = data:find("\r\n\r\n", 1, true)
+        if not part_header_end then
+          data, ok = add_data(data)
+          if not ok then
+            return false, "protocol violation: header does not end properly"
+          end
+        end
+      until part_header_end
+
+      -- parse the header of the new part
+      local header = {}
+      for k,v in data:sub(1,part_header_end+2):gmatch("([^%:\r\n]*)%s*%:%s*([^\r\n]*)\r\n") do
+        header[k] = v
+        local kupper = k:upper()
+        if (kupper=="CONTENT-DISPOSITION") then
+          name = v:match('name=%"([^%"]*)%"')
+          file_name = v:match('filename=%"([^%"]*)%"')
+        elseif (kupper=="CONTENT-TYPE") then
+          file_type = v
+        end
+      end
+
+      -- isolate the body of the new part
+      local part_body_end
+      data = data:sub(part_header_end+4)
+      repeat
+        part_body_end = data:find(b, 1, true)
+        if not part_body_end then
+          data, ok = add_data(data)
+          if not ok then
+            return false, "protocol violation: body does not end properly"
+          end
+        end
+      until part_body_end
+      local value = data:sub(1,part_body_end-1)
+      data = data:sub(part_body_end+#b)
+      data = add_data(data)
+
+      -- send the result to the caller
+      got_field(name, value, file_name, file_type)
+
+    end
+
+    return true, ""
+  end
+
+
+
+
+mg.write("HTTP/1.0 200 OK\r\n")
+mg.write("Connection: close\r\n")
+mg.write("Content-Type: text/plain; charset=utf-8\r\n")
+mg.write("Cache-Control: max-age=0, must-revalidate\r\n")
+mg.write("\r\n")
+
+
+mg.write("Parse request:\r\n")
+
+function fetch(k, v, fn, ft)
+    mg.write(k .. " = " .. v .. "\r\n")
+end
+
+ok, errtxt = parse_multipart_form(fetch)
+
+if not ok then
+    mg.write("Error: " .. errtxt .. "\r\n")
+else
+    mg.write("Parsing OK\r\n")
+end
+