Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@MichaelCurrie
Last active December 14, 2023 10:19
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save MichaelCurrie/19394abc19abd0de4473b595c0e37a3a to your computer and use it in GitHub Desktop.
Save MichaelCurrie/19394abc19abd0de4473b595c0e37a3a to your computer and use it in GitHub Desktop.
Drag-and-drop upload files, via JavaScript, to a simple Python 3 HTTP server
#Drag-and-drop upload files, via JavaScript, to a simple Python 3 HTTP server
body {
font-family: "Arial", sands-serif;
}
.dropzone {
width: 300px;
height: 300px;
border: 2px dashed #ccc;
color: #ccc;
line-height: 300px;
text-align: center;
}
.dropzone.dragover {
border-color: #000;
color: #000;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Drag n' Drop</title>
<link rel="stylesheet" type="text/css" href="dropzone.css" />
<script src="dropzone.js"></script>
</head>
<body>
<h1 id="title">Let's try some drag and drop uploading!</h1>
<div id="dropzone_element" class="dropzone">
Drop files here to upload
</div>
<div id="upload_results_element">
</div>
</body>
</html>
// Handle drag and drop into a dropzone_element div:
// send the files as a POST request to the server
"use strict";
// Only start once the DOM tree is ready
if(document.readyState === "complete") {
createDropzoneMethods();
} else {
document.addEventListener("DOMContentLoaded", createDropzoneMethods);
}
function createDropzoneMethods() {
let dropzone = document.getElementById("dropzone_element");
dropzone.ondragover = function() {
this.className = "dropzone dragover";
return false;
}
dropzone.ondragleave = function() {
this.className = "dropzone";
return false;
}
dropzone.ondrop = function(e) {
// Stop browser from simply opening that was just dropped
e.preventDefault();
// Restore original dropzone appearance
this.className = "dropzone";
upload_files(e.dataTransfer.files)
}
}
function upload_files(files) {
let upload_results = document.getElementById("upload_results_element");
let formData = new FormData(),
xhr = new XMLHttpRequest();
console.log("Dropped " + String(files.length) + " files.");
for(let i=0; i<files.length; i++) {
formData.append("file", files[i]);
}
xhr.onreadystatechange = function() {
if(xhr.readyState === XMLHttpRequest.DONE) {
alert(xhr.responseText);
}
console.log(xhr.response);
upload_results.innerHTML = this.response;
}
console.log("Let's upload files: ", formData);
xhr.open('POST', 'upload_handler.py', true); // async = true
xhr.send(formData);
}
# -*- coding: utf-8 -*-
import re
import sys
import os
import json
from http.server import SimpleHTTPRequestHandler, HTTPServer
class FileUploadHTTPRequestHandler(SimpleHTTPRequestHandler):
"""An HTTP Server that accepts POST requests and saves them as
files in the same folder as this script.
"""
protocol_version = "HTTP/1.1"
def do_POST(self):
"""Handle a POST request."""
# Save files received in the POST
wasSuccess, files_uploaded = self.handle_file_uploads()
# Compose a response to the client
response_obj = {
"wasSuccess": wasSuccess,
"files_uploaded": files_uploaded,
"client_address": self.client_address
}
response_str = json.dumps(response_obj)
self.log_message(response_str)
# Send our response code, header, and data
self.send_response(200)
self.send_header("Content-type", "Application/json")
self.send_header("Content-Length", len(response_str))
self.end_headers()
self.wfile.write(response_str.encode('utf-8'))
def read_line(self):
line_str = self.rfile.readline().decode('utf-8')
self.char_remaining -= len(line_str)
return line_str
def handle_file_uploads(self):
"""
Take the post request and save any files received to the same folder
as this script.
Returns
wasSuccess: bool: whether the process was a success
files_uploaded: list of string: files that were created
"""
self.char_remaining = int(self.headers['content-length'])
# Find the boundary from content-type, which might look like:
# 'multipart/form-data; boundary=----WebKitFormBoundaryUI1LY7c2BiEKGfFk'
boundary = self.headers['content-type'].split("=")[1]
basepath = self.translate_path(self.path)
# Strip this script's name from the path so it's just a folder
basepath = os.path.dirname(basepath)
# ----WebKitFormBoundaryUI1LY7c2BiEKGfFk
line_str = self.read_line()
if not boundary in line_str:
self.log_message("Content did NOT begin with boundary as " +
"it should")
return False, []
files_uploaded = []
while self.char_remaining > 0:
# Breaking out of this loop on anything except a boundary
# an end-of-file will be a failure, so let's assume that
wasSuccess = False
# Content-Disposition: form-data; name="file"; filename="README.md"
line_str = self.read_line()
filename = re.findall('Content-Disposition.*name="file"; ' +
'filename="(.*)"', line_str)
if not filename:
self.log_message("Can't find filename " + filename)
break
else:
filename = filename[0]
filepath = os.path.join(basepath, filename)
try:
outfile = open(filepath, 'wb')
except IOError:
self.log_message("Can't create file " + str(filepath) +
" to write; do you have permission to write?")
break
# Content-Type: application/octet-stream
line_str = self.read_line()
# Blank line
line_str = self.read_line()
# First real line of code
preline = self.read_line()
# Loop through the POST until we find another boundary line,
# signifying the end of this file and the possible start of another
while self.char_remaining > 0:
line_str = self.read_line()
# ----WebKitFormBoundaryUI1LY7c2BiEKGfFk
if boundary in line_str:
preline = preline[0:-1]
if preline.endswith('\r'):
preline = preline[0:-1]
outfile.write(preline.encode('utf-8'))
outfile.close()
self.log_message("File '%s' upload success!" % filename)
files_uploaded.append(filename)
# If this was the last file, the session was a success!
wasSuccess = True
break
else:
outfile.write(preline.encode('utf-8'))
preline = line_str
return wasSuccess, files_uploaded
if __name__ == "__main__":
httpd = HTTPServer(("", 8000), FileUploadHTTPRequestHandler)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\nKeyboard interrupt received, exiting.")
httpd.server_close()
sys.exit(0)
@MichaelCurrie
Copy link
Author

MichaelCurrie commented Jun 9, 2017

The JavaScript drag-and-drop example is from https://www.youtube.com/watch?v=hqSlVvKvvjQ, but in the video he used PHP code on the server side. I decided to use Python 3 instead.

I was already serving my folders statically using the following simple Python 3 server:

python -m http.server 8000

But this server doesn't handle POST requests, which is what the drag-and-drop Javascript client-side code generates.

So I looked up the source code for how that simple command actually works:

https://github.com/python/cpython/blob/3.4/Lib/http/server.py

I added the do_POST function from https://gist.github.com/UniIsland/3346170.

I modified @UniIsland's function to my coding tastes and also made multiple-file uploads possible.

To test, simply run the Python script and then point your browser to localhost:8000/dropzone.html

Then you should be able to drag-and-drop one or more files into the upload div element.

image

@venkatDesh
Copy link

it works fine.But if i try to upload images or .png files it doesnt work.The size of the image file at destination is 0KB.could you have any solution for that?

@MichaelCurrie
Copy link
Author

@venkatDesh I did a quick check in the code above and I can't see anything immediately obvious why it would specifically be causing problems for you for .png or other image formats but not other file extensions.

Sorry, I don't have time to investigate your error further, but if you find a solution please feel free to post it here to benefit others. Thanks for understanding.

@venkatDesh
Copy link

@MichaelCurrie i got the solution for that problem.By the wayThankyou for your reply.
I just changed the encode and decode method in upload_handle.py from 'utf-8' to 'ISO-8859-1' and it works fine.
And one more thing what if i want to custmozie the upload location while dragging do you have any idea for that?.

@MichaelCurrie
Copy link
Author

MichaelCurrie commented Sep 7, 2019

If you mean customize the place on the server the files are saved, you just need to change these lines:

basepath = self.translate_path(self.path)
# Strip this script's name from the path so it's just a folder
basepath = os.path.dirname(basepath)

To a hardcoded basepath:

basepath = os.path.join('C', 'venkatDesh', 'havefun')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment