📜 ⬆️ ⬇️

Perl - Sending files to the HTTP server by request

In fact, nothing original, documentation is enough, even more than, although later they cause a very strong longing. Therefore, it is easier to practically once show how and what is being done than to read and step on a rake ten times. Condition: There are some binary files that lie locally, there is a server to which we need to put these files, there is a script on this server (form handler) that is doing upload files
Task: These files need to be added via the web interface (form handler) to the server, as if we were a browser and sent the data through the form.

Using LWP :: UserAgent

Request generation algorithm:
A chore as XML :: LibXML, but reliable, so: Perl code (1)
 #! / usr / bin / perl

 use strict;
 use warnings;
 use LWP :: UserAgent;

 # This is the file that will be sent
 my $ file = './files/some_file.bin';
 # This is the URI to send the request to.
 my $ uri = 'http://somedomain.com/form/action/script';

 # Request object
 my $ request = HTTP :: Request-> new ('POST', $ uri);

 # Create a separator, because if we do not specify it forcibly, then with $ ua-> request ($ request) it will not be included in the main title
 # though, you can simply make $ request-> as_string after forming the header object, then it will be substituted by default, but we will not risk
 my $ boundary = 'X';
 my @rand = ('a' .. 'z', 'A' .. 'Z');
 for (0..14) {$ boundary. = $ rand [rand (@rand)];}

 # Create a title:
 $ request-> header ('Content-Type' => 'multipart / form-data; boundary ='. $ boundary);
 $ request-> header ('User-Agent' => '<i> Mozilla Firefox 5.0: -) </ i>');
 $ request-> header ('Referer' => '<i> http://somedomain.com/form </ i>');
 $ request-> protocol ('HTTP / 1.0');  # Although it will make LWP :: UserAgent, but better immediately

 # Formation of ordinary, textual form parameters
 my $ field = HTTP :: Message-> new (
                                 [
                                     'Content-Disposition' => 'form-data;  name = "<i> fieldname </ i>" ',
                                     'Content-Type' => 'text / plain;  charset = utf-8 ',
                                 ]);  # The HTTP header :: Headers is substituted during the creation of the HTTP :: Message object
 $ field-> add_content_utf8 ('<i> somevalue </ i>');  # As you can see, somevalue should be in UTF-8
 $ request-> add_part ($ field);
 # ... And so on, for each text field ...

 # Formation of binary form parameters
 open (my $ fh, '<', $ file);
 # And you can first make a header, and then apply to HTTP :: Message
     my $ size = (stat $ file) [7];
     my $ header = HTTP :: Headers-> new;
     $ header-> header ('Content-Disposition' => 'form-data; name = "<i> file </ i>"; filename = "<i> somefile.bin </ i>'); # Although filename can be calculated from the file name
     $ header-> header ('Content-Type' => '<i> application / octet-stream </ i>');  # Or appropriate file type
     my $ file_content = HTTP :: Message-> new ($ header);
     $ file_content-> add_content ($ _) while <$ fh>;
     $ request-> add_part ($ file_content);
 close $ fh;
 # ... And so on, for each file ...

 my $ response = $ ua-> request ($ request);
 if ($ response-> is_success) {
     print $ response-> content
 } else {
     die $ response-> status_line
 } 

Use socket

Everything would be fine, but if we have a large file for transmission, then uploading it as a whole into memory to collect the request is not the best prospect. Therefore, it is possible to send a request by stream through a socket: Perl code (2)
 #! / usr / bin / perl

 use strict;
 use warnings;
 use HTTP :: Headers;
 use HTTP :: Message;
 use HTTP :: Request;
 use HTTP :: Response;
 use IO :: Socket :: INET;

 # This is the file that will be sent
 my $ file = './files/some_file.bin';
 # This is the URI to send the request to.
 my $ uri = 'http://somedomain.com/form/action/script';
 # Since we will use a socket, we need a domain, port and path separately
 my ($ domain, $ port, $ path) = $ uri = ~ m / ^ (?: https? \: \ / \ /)? ([^ \ / \:] +) (?: \: (\ d + ))? (. +) $ /;
 $ port || = 80;  # Default
    
 # A bicycle is, of course, good, but you can easily form headers and non-binary request body using a ready-made module
 my $ header = HTTP :: Headers-> new;  $ header-> header ('Content-Type' => 'multipart / form-data');
 my $ request = HTTP :: Request-> new ('POST', $ uri, $ header);  # Instead of $ path we have $ uri, so it should be ;-)
 $ request-> protocol ('HTTP / 1.0');  # It is strange that the default protocol does not set the HTTP :: Request protocol, therefore we set ourselves

 # For small amounts of data, such as text fields, the bike will also be superfluous
 # (SFCI) Conditions are the same as in the previous code (1)
 my $ field = HTTP :: Message-> new (
                                 [
                                     'Content-Disposition' => 'form-data;  name = "<i> fieldname </ i>" ',
                                     'Content-Type' => 'text / plain;  charset = utf-8 ',
                                 ]);
 $ field-> add_content_utf8 ('<i> somevalue </ i>');  # And here, too, utf8
 $ request-> add_part ($ field);
 # ... And so on, for each text field ...

 Further, our request, but without the files we divide into the main title and the first part of the content
 # We divide the regular expression for $ request-> headers-> as_string does not return the first line of the request, namely, the POST command,
 # and collect the string yourself, you can of course, but laziness.
 my ($ head, $ content) = $ request-> as_string = ~ m / ^ (. +?) \ n \ n (. +) $ / s;
 Content is not finished here, so we cut off - [LF] [EOF]
 $ content = substr ($ content, 0, -4);
 # as well as the boundary
 $ content = ~ s / (\ - \ - [^ \ n] +) $ // s;
 my $ boundary = $ 1;
 # We consider the preliminary length of the request
 my $ length = length $ content;

 # Now our files:
 my $ files = [];
 my $ size = (stat $ file) [7];
 my $ f_header = HTTP :: Headers-> new;
 $ f_header-> header ('Content-Disposition' => 'form-data; name = "<i> file </ i>"; filename = "<i> somefile.bin </ i>');
 $ f_header-> header ('Content-Type' => '<i> application / octet-stream </ i>');
 $ f_header = $ boundary. "\ n". $ f_header-> as_string. "\ n";
 # We arrive at the length of the request
 $ length + = length $ f_header;
 $ length + = $ size;
 # Actually, the procedure below is only for cases when there are many files.
 # Then, we first need to calculate the length of the content, because it (the length) will be indicated in the main title
 push @ {$ files}, {header => $ f_header, file => $ file};
 # ... And so on, for each file ...

 # So we have everything ready
 $ length + = length $ boundary .'-- ';  # The end line is also considered
 # Open the socket
 my $ socket = IO :: Socket :: INET-> new ($ domain. ':'. $ port) ||  die $ !;
 # To the main title is long
 $ head. = "\ nContent-Length:". $ length;
 # Send the header and the first (text) part of the content to the socket
 print $ socket $ head;
 print $ socket "\ n \ n";
 print $ socket $ content;

 foreach my $ file (@ {$ files}) {
     print $ socket $ file -> {header};
     open (my $ fh, '<', $ file -> {file});
     print $ socket $ _ while <$ fh>;
     print $ socket "\ n";
     close $ fh;
 }

 # Send socket end of file
 print $ socket $ boundary .'-- '; 
 # Send socket end of file
 shutdown ($ socket, 1);
 # Get the answer from the socket and parse it
 my $ response = HTTP :: Response-> parse (join ('', <$ socket>));
 if ($ response-> is_success) {
     print $ response-> content
 } else {
     die $ response-> status_line
 }
    
Well done (SFCI) I want to note that everything in the code in italics should be replaced with the corresponding values ​​of the problem condition. Note: SFCI - Special for copipaster's idiots. No comments.

')

Source: https://habr.com/ru/post/63432/


All Articles