inets httpd cgi script: How do you retrieve json data? - apache

The cgi scripts that I have tried are unable to retrieve json data from my inets httpd server.
In order to retrieve json data in a cgi script, you need to be able to read the body of the request, which will contain something like:
{"a": 1, "b": 2}
With a perl cgi script, I can read the body of a request like this:
my $cgi = CGI->new;
my $req_body = $cgi->param('POSTDATA');
I assume that is an indirect way of reading what the server pipes to the script's stdin because in a python cgi script I have to write:
req_body = sys.stdin.read()
When I request a cgi script from an apache server, my perl and python cgi scripts can successfully get the json data from apache. But when I request the same cgi scripts from my inets httpd server, my perl cgi script reads nothing for the request body, and my python cgi script hangs then the server times out. My cgi scripts are able to retrieve data formatted as "a=1&b=2" from an inets httpd server--in that case the cgi facilities in both perl and python automatically parse the data for me, so instead of trying to read the body of the request, I just access the structures that cgi created.
Here is my httpd sever configuration (server.conf):
[
{modules, [
mod_alias,
mod_actions,
mod_esi,
mod_cgi,
mod_get,
mod_log
]},
{bind_address, "localhost"},
{port,0},
{server_name,"httpd_test"},
{server_root,"/Users/7stud/erlang_programs/inets_proj"},
{document_root,"./htdocs"},
{script_alias, {"/cgi-bin/", "/Users/7stud/erlang_programs/inets_proj/cgi-bin/"} },
{erl_script_alias, {"/erl", [mymod]} },
{erl_script_nocache, true},
{error_log, "./errors.log"},
{transfer_log, "./requests.log"}
].
I start my httpd server with this program (s.erl):
-module(s).
-compile(export_all).
%Need to look up port with httpd:info(Server)
ensure_inets_start() ->
case inets:start() of
ok -> ok;
{error,{already_started,inets}} -> ok
end.
start() ->
ok = ensure_inets_start(),
{ok, Server} = inets:start(httpd,
[{proplist_file, "./server.conf"}]
),
Server.
stop(Server) ->
ok = inets:stop(httpd, Server).
My cgi script (1.py):
#!/usr/bin/env python3
import json
import cgi
import cgitb
cgitb.enable() #errors to browser
import sys
sys.stdout.write("Content-Type: text/html")
sys.stdout.write("\r\n\r\n")
#print("<div>hello</div>")
req_body = sys.stdin.read()
my_dict = json.loads(req_body)
if my_dict:
a = my_dict.get("a", "Not found")
b = my_dict.get("b", "Not found")
total = a + b
print("<div>Got json: {}</div>".format(my_dict) )
print("<div>a={}, b={}, total={}</div>".format(a, b, total))
else:
print("<div>Couldn't read json data.</div>")
My cgi script (1.pl):
#!/usr/bin/env perl
use strict;
use warnings;
use 5.020;
use autodie;
use Data::Dumper;
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use JSON;
my $q = CGI->new;
print $q->header,
$q->start_html("Test Page"),
$q->h1("Results:"),
$q->div("json=$json"),
$q->end_html;
Server startup in terminal window:
~/erlang_programs/inets_proj$ erl
Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.2 (abort with ^G)
1> c(s).
s.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,s}
2> Server = s:start().
<0.86.0>
3> httpd:info(Server).
[{mime_types,[{"htm","text/html"},{"html","text/html"}]},
{server_name,"httpd_test"},
{erl_script_nocache,true},
{script_alias,{"/cgi-bin/",
"/Users/7stud/erlang_programs/inets_proj/cgi-bin/"}},
{bind_address,{127,0,0,1}},
{modules,[mod_alias,mod_actions,mod_esi,mod_cgi,mod_get,
mod_log]},
{server_root,"/Users/7stud/erlang_programs/inets_proj"},
{erl_script_alias,{"/erl",[mymod]}},
{port,51301},
{transfer_log,<0.93.0>},
{error_log,<0.92.0>},
{document_root,"./htdocs"}]
4>
curl request:
$ curl -v \
> -H 'Content-Type: application/json' \
> --data '{"a": 1, "b": 2}' \
> http://localhost:51301/cgi-bin/1.py
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 51301 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 51301 (#0)
> POST /cgi-bin/1.py HTTP/1.1
> Host: localhost:51301
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 16
>
* upload completely sent off: 16 out of 16 bytes
===== hangs for about 5 seconds ====
< HTTP/1.1 504 Gateway Time-out
< Date: Thu, 08 Mar 2018 11:02:27 GMT
< Content-Type: text/html
< Server: inets/6.4.5
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection 0
$
My directory structure:
~/erlang_programs$ tree inets_proj/
inets_proj/
├── apache_cl.erl
├── cgi-bin
│   ├── 1.pl
│   └── 1.py
├── cl.beam
├── cl.erl
├── errors.log
├── htdocs
│   └── file1.txt
├── mylog.log
├── mymod.beam
├── mymod.erl
├── old_server.conf
├── old_server3.conf
├── old_server4.conf
├── requests.log
├── s.beam
├── s.erl
├── server.conf
└── urlencoded_post_cl.erl

I dug up the RFC for the cgi spec, which says:
RFC 3875 CGI Version 1.1 October 2004
4.2. Request Message-Body
Request data is accessed by the script in a system-defined method;
unless defined otherwise, this will be by reading the 'standard
input' file descriptor or file handle.
Request-Data = [ request-body ] [ extension-data ]
request-body = <CONTENT_LENGTH>OCTET
extension-data = *OCTET
A request-body is supplied with the request if the CONTENT_LENGTH is
not NULL. The server MUST make at least that many bytes available
for the script to read. The server MAY signal an end-of-file
condition after CONTENT_LENGTH bytes have been read or it MAY supply
extension data. Therefore, the script MUST NOT attempt to read more
than CONTENT_LENGTH bytes, even if more data is available. However,
it is not obliged to read any of the data.
I don't understand what extension data is, but the key line is:
the [cgi] script MUST NOT attempt to read more than CONTENT_LENGTH
bytes, even if more data is available.
If I alter my python script to read in the content length rather than trying to read in the whole stdin file--which doesn't stop reading until it gets an eof signal--then my python cgi script successfully retrieves the json data from my inets httpd server.
#!/usr/bin/env python3
import json
import sys
import os
content_len = int(os.environ["CONTENT_LENGTH"])
req_body = sys.stdin.read(content_len)
my_dict = json.loads(req_body)
sys.stdout.write("Content-Type: text/html")
sys.stdout.write("\r\n\r\n")
if my_dict:
a = my_dict.get("a", "Not found")
b = my_dict.get("b", "Not found")
total = a + b
print("<div>Content-Length={}</div".format(content_len))
print("<div>Got json: {}</div>".format(my_dict) )
print("<div>a={}, b={}, total={}</div>".format(a, b, total))
else:
print("<div>Couldn't read json data.</div>")
'''
form = cgi.FieldStorage()
if "a" not in form:
print("<H1>Error:</H1>")
print("<div>'a' not in form</div>")
else:
print("<p>a:{}</p>".format( form["a"].value) )
if "b" not in form:
print("<H1>Error:</H1>")
print("<div>'b' not in form</div>")
else:
print("<p>b:{}</p>".format(form["b"].value) )
'''
Server info:
4> httpd:info(Server).
[{mime_types,[{"htm","text/html"},{"html","text/html"}]},
{server_name,"httpd_test"},
{erl_script_nocache,true},
{script_alias,{"/cgi-bin/",
"/Users/7stud/erlang_programs/inets_proj/cgi-bin/"}},
{bind_address,{127,0,0,1}},
{modules,[mod_alias,mod_actions,mod_esi,mod_cgi,mod_get,
mod_log]},
{server_root,"/Users/7stud/erlang_programs/inets_proj"},
{erl_script_alias,{"/erl",[mymod]}},
{port,65451},
{transfer_log,<0.93.0>},
{error_log,<0.92.0>},
{document_root,"./htdocs"}]
5>
curl request (note that curl automatically calculates the content length and puts it in a Content-Length header):
~$ curl -v \
> -H 'Content-Type: application/json' \
> --data '{"a": 1, "b": 2}' \
> http://localhost:65451/cgi-bin/1.py
* Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 65451 failed: Connection refused
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 65451 (#0)
> POST /cgi-bin/1.py HTTP/1.1
> Host: localhost:65451
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 16
>
* upload completely sent off: 16 out of 16 bytes
< HTTP/1.1 200 OK
< Date: Fri, 09 Mar 2018 04:36:42 GMT
< Server: inets/6.4.5
< Transfer-Encoding: chunked
< Content-Type: text/html
<
<div>Content-Length=16</div
<div>Got json: {'a': 1, 'b': 2}</div>
<div>a=1, b=2, total=3</div>
* Connection #0 to host localhost left intact
~$
Here's the perl script that I got work with inets httpd (1.pl):
#!/usr/bin/env perl
use strict;
use warnings;
use 5.020;
use autodie;
use Data::Dumper;
use JSON;
if (my $content_len = $ENV{CONTENT_LENGTH}) {
read(STDIN, my $json, $content_len);
my $href = decode_json($json);
my $a = $href->{a};
my $b = $href->{b};
print 'Content-type: text/html';
print "\r\n\r\n";
print "<div>a=$a</div>";
print "<div>b=$b</div>";
#my $q = CGI->new; #Doesn't work with inets httpd server
#my $q = CGI->new(''); #Doesn't try to read from stdin, do does work.
# print $q->header,
# $q->start_html("Test Page"),
# $q->div("json=$json"),
# $q->div("a=$a"),
# $q->div("b=$b"),
# $q->div("total=$total"),
# $q->end_html;
}
else {
my $error = "Could not read json: No Content-Length header in request.";
print 'Content-type: text/html';
print "\r\n\r\n";
print "<div>$error</div>";
# my $q = CGI->new;
# print $q->header,
# $q->start_html("Test Page"),
# $q->h1($error),
# $q->end_html;
}
I couldn't get perl's CGI module to work in conjunction with reading from STDIN. Edit: A kind soul at perlmonks helped me solve that one:
my $q = CGI->new('');
The blank string tells CGI->new not to read from stdin and parse the data.

Related

Error when using curl inside terraform to call API

I am trying to make a POST request to Azure DevOps API using curl code within a terraform script.
resource "null_resource" "AzDo_API_request" {
provisioner "local-exec" {
command = <<EOT
curl -L -X POST "https://dev.azure.com/altabhussain0027/_apis/hooks/subscriptions?api-version=7.1-preview.1"
-H "Authorization: Basic OnBobHdxcm1sb2Fvb3ZwZDZmbHZobjdiMnNoYXJ2aTRqdWNsYjJqNnpyemQ3dG9jYWh0eGE="
-H "Content-Type: application/json"
--data-raw "{
\"consumerActionId\": \"httpRequest\",
\"consumerId\": \"webHooks\",
\"consumerInputs\": {
\"url\": \"https://pulse-xxx.com/webhook/c29ac65b-xxxx-xxxx-xxxx-f4dc16d3114e\"
},
\"eventType\": \"workitem.updated\",
\"publisherId\": \"tfs\",
\"publisherInputs\": {
\"areaPath\": \"any\",
\"workItemType\": \"Feature\",
\"projectId\": \"b0eb9e33-xxxx-xxx-xxxx-68866d0dd821\"
},
\"resourceVersion\": \"1.0\",
\"scope\": \"all\"
}"
EOT
}
}
The above curl code is generated from Postman wherein the POST request are successfull without giving error.
However, when executing the same using terraform, I am getting the below error:
null_resource.AzDo_API_request (local-exec): curl: (3) URL using bad/illegal format or missing URL
│ Error: local-exec provisioner error
│
│ with null_resource.AzDo_API_request,
│ on servicehook.tf line 2, in resource "null_resource" "AzDo_API_request":
I am running the above in Windows 11 machine, it might have to do with the double quotes. Any help is appreciated.

Apache unable to load perl module

I am experimenting with setting up an Apache server and running some perl scripts on it, but I'm running into some issues with my Apache and Perl config.
Initially, I was getting a 500 server error when trying to run a cgi script, and the error log showed that Apache was looking in the wrong #INC for a module I was running.
So, in httpd.conf, I added this line:
SetEnv PERL5LIB /Users/rasha/.plenv/versions/5.34.0/lib/perl5/site_perl/5.34.0/darwin-2level:/Users/rasha/.plenv/versions/5.34.0/lib/perl5/site_perl/5.34.0:/Users/rasha/.plenv/versions/5.34.0/lib/perl5/5.34.0/darwin-2level:/Users/rasha/.plenv/versions/5.34.0/lib/perl5/5.34.0
which I got by running: perl -e 'print join "\n", #INC;'
Now I am still getting a 500 server error, but with a different error message:
[Tue Feb 22 14:24:32.919661 2022] [cgi:error] [pid 35434] [client 127.0.0.1:55187] AH01215: Can't load '/Users/rasha/.plenv/versions/5.34.0/lib/perl5/site_perl/5.34.0/darwin-2level/auto/List/Util/Util.bundle' for module List::Util: dlopen(/Users/rasha/.plenv/versions/5.34.0/lib/perl5/site_perl/5.34.0/darwin-2level/auto/List/Util/Util.bundle, 0x0001): symbol not found in flat namespace '_PL_DBsub' at /Users/rasha/.plenv/versions/5.34.0/lib/perl5/5.34.0/XSLoader.pm line 96.: /usr/local/var/www/cgi-bin/test
The test script I am trying to run:
#!/usr/bin/env perl
use strict;
use warnings;
use DBI;
use CGI qw(:standard);
my ($dbh, $sth, $count);
$dbh = DBI->connect("DBI:mysql:host=localhost;database=xxxx",
"xxxx", "xxxx",
{PrintError => 0, RaiseError => 1});
$sth = $dbh->prepare("Select name, wins, losses from teams");
$sth->execute;
print header, start_html("team data");
$count = 0;
while (my #val = $sth->fetchrow_array) {
print p (sprintf ("name = %s, wins = %d, losses = %d\n", $val[0], $val[1], $val[2]));
$count++;
};
print p("$count rows total"), end_html;
$sth->finish;
$dbh->disconnect;
Does anyone have any ideas what the issue might be?

Apache2 .cgi program fails to open file in /tmp (raspbian)

Edit: OK, I've made it much simpler.
I put the file first.pl into /usr/lib/cgi-bin (typed, not copied, hope it's error free)
#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "Hello, World.";
my $file = "/tmp/first";
open(FILE, '>'.$file);
print FILE "Hello me\n";
close FILE;
I run the file, it prints and creates the file /tmp/first.
Now I go to my browser, to localhost/cgi-bin/first.pl. I see "Hello, World." I see no file /tmp/first created. I also tried a find / -name first 2>/dev/null to see if that would find the file. No luck.
Please, what's going on?
Another edit: I added code to print the environment to first.pl I don't see anything interesting
Hello, World.
DOCUMENT_ROOT = /var/www/html
REQUEST_URI = /cgi-bin/first.pl
QUERY_STRING =
HTTP_DNT = 1
HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
REMOTE_PORT = 59742
HTTP_USER_AGENT = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36
SERVER_ADMIN = webmaster#localhost
HTTP_ACCEPT_ENCODING = gzip, deflate
SCRIPT_FILENAME = /usr/lib/cgi-bin/first.pl
SCRIPT_NAME = /cgi-bin/first.pl
REQUEST_SCHEME = http
SERVER_PORT = 80
SERVER_NAME = se0
SERVER_SOFTWARE = Apache/2.4.38 (Raspbian)
HTTP_CACHE_CONTROL = max-age=0
HTTP_CONNECTION = keep-alive
PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
CONTEXT_PREFIX = /cgi-bin/
REMOTE_ADDR = 192.168.0.116
GATEWAY_INTERFACE = CGI/1.1
HTTP_HOST = se0
HTTP_UPGRADE_INSECURE_REQUESTS = 1
HTTP_ACCEPT_LANGUAGE = en-US,en;q=0.9
CONTEXT_DOCUMENT_ROOT = /usr/lib/cgi-bin/
SERVER_ADDR = 192.168.0.39
REQUEST_METHOD = GET
SERVER_SIGNATURE =
Apache/2.4.38 (Raspbian) Server at se0 Port 80
SERVER_PROTOCOL = HTTP/1.1
(original follows)
I have a cgi program that is supposed to be creating a file in /tmp, but it never shows up. This same program has worked properly on other Linux systems.
The code in question is
if (strlen(ffileName)) {
if (getTempFileName(tfileName) != cgiParseSuccess) {
return cgiParseIO;
}
outf = fopen(tfileName, "w+b");
I can use gdb to step through this code, like so:
Breakpoint 1, cgiParsePostMultipartInput () at cgic.c:535
535 outf = fopen(tfileName, "w+b");
(gdb) p tfileName
$3 = "/tmp/cgicyMuPrt\000\350\000\000\000\350\000\000\000\004\000\000\000\001\000\000\000\004\000\000\000\024\000\000\000\003\000\000\000GNU\000\251\f\316\022\362\317x\362w\276\246\211\345\265\031-\036\261\204\354\003\000\000\000\a\000\000\000\002\000\000\000\006\000\000\000\005#Ex\021\000\021\000\a\000\000\000\n\000\000\000\f\000\000\000\060\270\202\r\200\020ؽ\235\273\223\034\032\237ֽ\237\354В\220u\202\r%u\202\r", '\000' <repeats 20 times>, "t\003\000\000\000\000\000\000\003\000\t\000\000\000\000\000$#\001\000\000\000\000\000\003\000\023\000F", '\000' <repeats 11 times>, "\"\000\000\000\020", '\000' <repeats 11 times>...
(gdb) n
540 result = afterNextBoundary(mpp, outf, &out, &bodyLength, 0);
(gdb) p outf
$4 = (FILE *) 0x1968158
At this point, I do a ls of /tmp, and I do not see the file which should have been created. Yet it looks like there was no error. Where did my file go?
pi#raspberrypi:/etc $ ls -l / | grep tmp
drwxrwxrwt 18 root root 4096 Feb 10 20:50 tmp
pi#raspberrypi:/etc $ ls /tmp
dhcpcd-pi
ssh-ANJEEYvUhJSE
ssh-r2ofFbnIBLFA
systemd-private-0c86de6f48a34f97b2f57adac8054087-apache2.service-COknFK
systemd-private-0c86de6f48a34f97b2f57adac8054087-colord.service-yytb8l
systemd-private-0c86de6f48a34f97b2f57adac8054087-systemd-timesyncd.service-DM6doz
This is raspbian version 10 "Buster".
I'm using apache2 to run my c .cgi program, which is doing other things properly.
Some googling mentions SELinux. That does not seem to be installed.
The code comes from https://github.com/boutell/cgic, version 2.05
The answer to the question is here:
https://serverfault.com/questions/912094/apache2-saves-files-on-tmp-in-a-system-private-hash-instead-of-just-saving
Well, I found where the file went. For the perl program, it ended up in (oooh, I don't want to type the whole thing, I will abbreviate)(the raspbian browser doesn't seem to work at this site)
/tmp/systemd-private-0c86...4087-apache2.service-first/tmp/first
and my .cgi files are there too. That systemd-... directory is owned by root, with no non-root privileges.
So I guess this has turned into a systemd question, and I will start googling that.
Or, I will just create a directory named /temp and put the files there. That seems to work.

Why POST endpoint not being invoked but GET endpoint is being invoked - jersey container grizzly2

I can't figure out why my GET endpoint gets called but my POST endpoint is not working. When I call curl -v -X GET http://localhost:8080/myresource/test123 it succesfully returns hello
But when I call
curl -v -X POST \
http://localhost:8080/myresource \
-H 'Content-Type: application/json' \
-d '{"test": "testvalue"}'
I keep getting this response:
* Connected to localhost (::1) port 8080 (#0)
> POST /myresource HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 21
>
* upload completely sent off: 21 out of 21 bytes
< HTTP/1.1 500 Request failed.
< Content-Type: text/html;charset=ISO-8859-1
< Connection: close
< Content-Length: 1031
<
* Closing connection 0
<html><head><title>Grizzly 2.4.0</title><style><!--div.header {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#003300;font-size:22px;-moz-border-radius-topleft: 10px;border-top-left-radius: 10px;-moz-border-radius-topright: 10px;border-top-right-radius: 10px;padding-left: 5px}div.body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:#FFFFCC;font-size:16px;padding-top:10px;padding-bottom:10px;padding-left:10px}div.footer {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#666633;font-size:14px;-moz-border-radius-bottomleft: 10px;border-bottom-left-radius: 10px;-moz-border-radius-bottomright: 10px;border-bottom-right-radius: 10px;padding-left: 5px}BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;}B {font-family:Tahoma,Arial,sans-serif;color:black;}A {color : black;}HR {color : #999966;}--></style> </head><body><div class="header">Request failed.</div><div class="body">Request failed.</div><div class="footer">Grizzly 2.4.0</div></body></html>%
Here is my code
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
#Path("myresource")
class HelloWorldResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
fun createMessage(testPost: String): Response {
return Response.status(200).entity("helllo post").build()
}
#GET
#Path("{testGet}")
#Produces(MediaType.APPLICATION_JSON)
fun getMessage(#PathParam("testGet") testGet: String): Response {
return Response.status(200).entity("hello").build()
}
}
Without seeing the actual underlying exception, it's hard to say for sure, but likely you're running into similar issues as Jersey: Return a list of strings with mismatches between a String type and a MediaType.APPLICATION_JSON produces/consumes declarations. If you're dealing in raw strings, I'd suggest using MediaType.PLAIN_TEXT, or having your post body and return value be an entity that can be represented as a non-raw-string json object (ie, something enclosed in {}), and making sure a jackson provider is registered with jersey.

How should a GraphQL request for file upload look like?

I use Graphene on the server side with similar code to the one from documentation:
class UploadFile(graphene.ClientIDMutation):
class Input:
pass
# nothing needed for uploading file
# your return fields
success = graphene.String()
#classmethod
def mutate_and_get_payload(cls, root, info, **input):
# When using it in Django, context will be the request
files = info.context.FILES
# Or, if used in Flask, context will be the flask global request
# files = context.files
# do something with files
return UploadFile(success=True)
It's all clear, but how should the request look like ?
I've seen people suggesting multipart/form-data, but AFAIK that requires additional layer to parse the multipart request, so that's probably not what I need.. or is it ? :
curl -X "POST" "http://127.0.0.1:5001/graphql" \
-H 'Content-Type: multipart/form-data; boundary=----GraphQLFileUpload' \
-F "operations={\"query\":\"mutation ($files: [Upload!]!) {uploadFile(selfie: $file) {status}}\",\"variables\":{}}" \
-F "map={\"x\":[\"variables.files.x\"]}" \
-F "x=#/tmp/dummy.jpg "
I'll reply myself. The curl code I had was based on an external library that confused the hell out of me.
Here's my solution that doesn't require any additional library:
Python server code (graphene):
class UploadImage(graphene.Mutation):
class Arguments(object):
file = graphene.String(required=True)
status = graphene.Boolean()
def mutate(self, info, file):
img = info.context.files[file].read()
# more stuff
return UploadImage(status=True)
Curl request (multipart form)
curl -X POST http://localhost:5001/graphql \
-H 'content-type: multipart/form-data; boundary=----GraphQlFileUpload' \
-F 'query=mutation {uploadImage(file: "photo") {status}}' \
-F 'photo=#selfie.jpg'