HTTPRequest or HTTPClient for upload file to server

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By wojtasss

it is possible to upload file in godot using HTTPRequest node or I need use HTTPClient and do everything from scratch building raw http multipart request?

For small files, you could send a POST request using HTTPRequest and pass the binary data converted to Base64 in the request data using Marshalls.raw_to_base64(array: PoolByteArray). (This will increase the payload size by at least 33%.)

Calinou | 2020-08-07 13:51

Thanks @Callinou for for answer, but I have figured out how to make it with multipart form-data

wojtasss | 2020-08-07 14:34

:bust_in_silhouette: Reply From: wojtasss

I have found solution myself, here you are, this code is sending directly icon.png from res:// code doesn’t processing resposne, just send request as multipart form-data by HTTPClient.raw_request, as server backend I am using Rails with JSON format endpoint /images

func _ready():
	var file = File.new()
	file.open('res://icon.png', File.READ)
	var file_content = file.get_buffer(file.get_len())
	
	var body = PoolByteArray()
	body.append_array("\r\n--WebKitFormBoundaryePkpFF7tjBAqx29L\r\n".to_utf8())
	body.append_array("Content-Disposition: form-data; name=\"image\"; filename=\"icon.png\"\r\n".to_utf8())
	body.append_array("Content-Type: image/png\r\n\r\n".to_utf8())
	body.append_array(file_content)
	body.append_array("\r\n--WebKitFormBoundaryePkpFF7tjBAqx29L--\r\n".to_utf8())
	
	var headers = [
		"Content-Type: multipart/form-data;boundary=\"WebKitFormBoundaryePkpFF7tjBAqx29L\""
	]
	var http = HTTPClient.new()
	http.connect_to_host("http://localhost", 3000, false)
			
	while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING:
		http.poll()
		OS.delay_msec(500)

	assert(http.get_status() == HTTPClient.STATUS_CONNECTED) # Could not connect

	var err = http.request_raw(HTTPClient.METHOD_POST, "/images" , headers, body)
	
	assert(err == OK) # Make sure all is OK.

	while http.get_status() == HTTPClient.STATUS_REQUESTING:
		# Keep polling for as long as the request is being processed.
		http.poll()
		if not OS.has_feature("web"):
			OS.delay_msec(500)
		else:
			yield(Engine.get_main_loop(), "idle_frame")
:bust_in_silhouette: Reply From: mingganglee

Godot

extends Node


func _ready():
	var request = HTTPRequest.new()
	request.connect("request_completed", self, "_request_callback")
	add_child(request)
	
	upload_file(request)


func _request_callback(result, response_code, headers, body) -> void:
	if response_code == HTTPClient.RESPONSE_OK:
		var response = str2var(body.get_string_from_utf8())
		prints("response", response)
	elif response_code == HTTPClient.STATUS_DISCONNECTED:
		prints("not connected to server")


func upload_file(request: HTTPRequest) -> void:
	var file_name = "icon.png"
	var file = File.new()
	file.open('res://%s' % file_name, File.READ)
	var file_content = file.get_buffer(file.get_len())

	var body = PoolByteArray()
	body.append_array("\r\n--BodyBoundaryHere\r\n".to_utf8())
	body.append_array(("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n" % file_name).to_utf8())
	body.append_array("Content-Type: image/png\r\n\r\n".to_utf8())
	body.append_array(file_content)
	body.append_array("\r\n--BodyBoundaryHere--\r\n".to_utf8())

	var headers = [
		"Content-Type: multipart/form-data; boundary=BodyBoundaryHere"
	]
	
	var error = request.request_raw("http://localhost:5000", headers, true, HTTPClient.METHOD_POST, body)
	if error != OK:
		push_error("An error occurred in the HTTP request.")
	pass

Python - Simple Service

import os

from flask import Flask, flash, request, redirect, url_for, jsonify
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = '.'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'dat'}

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER


def allowed_file(filename):
    return '.' in filename and \
            filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route("/", methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            return jsonify({"code": 401, "msg": "No file part"})
        file = request.files['file']

        if file.filename == '':
            return jsonify({"code": 401, "msg": "No selected file"})

        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return jsonify({"code": 200, "msg": "Upload Success"})
        return jsonify({"code": 401, "msg": "Upload file formating error."})


if __name__ == "__main__":
    app.run("0.0.0.0", 5000, False)