📜 ⬆️ ⬇️

Almost a web-server with your own hands

Recently, there have been several posts on attracting attention to certain programming languages ​​using the example of writing some simple “web-server”. Since this perl booze has not yet been touched, I’ll add my five kopecks :)
We will write a simple server application masquerading as an http-server.


Our "server" will listen to the local port 8080 and greet all those who applied, or give the contents of the requested file if available (in the folder from which the script server was launched).

For a start, simple and short code. I think at this stage, especially comment does not require anything, so leave commenting for later.
  1. #! / usr / bin / perl
  2. use LWP :: Socket ;
  3. $ headers = "HTTP / 1.1 200 OK \ r \ n Content-Type: text / html \ r \ n \ r \ n " ;
  4. $ sock = new LWP :: Socket ( ) ;
  5. $ sock -> bind ( '127.0.0.1' , '8080' ) ;
  6. $ sock -> listen ( 10 ) ;
  7. while ( $ socket = $ sock -> accept ( 10 ) ) {
  8. $ content = "Hello from Habr" ;
  9. $ file_name ; $ socket -> read ( \ $ file_name ) ;
  10. $ file_name = ~ s / GET \ / ([^] *) HTTP. + / $ 1 / s ;
  11. if ( open FILE , '<' , $ file_name ) {
  12. $ content = join "" , <FILE> ; close FILE ;
  13. }
  14. $ socket -> write ( $ headers . $ content ) ;
  15. $ socket -> shutdown ( ) ;
  16. }

This is an ugly code, so it’s better not to write it, this example is only to show that the required functionality, if necessary, can be implemented fairly quickly and with a small amount of code.
')
This code should be refined and supplemented. Let's make it a bit more readable, add a check whether it was possible to bind to the specified port (otherwise it is already busy), we will check the existence of the file before trying to open it, well, it will use the strict and warnings pragmas.
  1. #! / usr / bin / perl
  2. use strict ;
  3. use warnings ;
  4. use LWP :: Socket ;
  5. my $ headers = "HTTP / 1.1 200 OK \ r \ n Content-Type: text / html \ r \ n \ r \ n " ;
  6. my $ sock = new LWP :: Socket ( ) ;
  7. die "Can't bind a socket" unless $ sock -> bind ( '127.0.0.1' , '8080' ) ;
  8. $ sock -> listen ( 10 ) ;
  9. while ( my $ socket = $ sock -> accept ( 10 ) ) {
  10. my $ content = "Hello from Habr" ;
  11. my $ file_name ;
  12. $ socket -> read ( \ $ file_name ) ;
  13. $ file_name = ~ s / GET \ / ([^] *) HTTP. + / $ 1 / s ;
  14. if ( - f $ file_name and open FILE , '<' , $ file_name ) {
  15. $ content = join "" , <FILE> ;
  16. close FILE ;
  17. }
  18. $ socket -> write ( $ headers . $ content ) ;
  19. $ socket -> shutdown ( ) ;
  20. }
  21. $ sock -> shutdown ( ) ;

Just a few lines more, but the script has become a bit more cultured.

Here we have

use LWP::Socket;
we connect the module we need. It is quite simple to use, so I chose it.

my $sock = new LWP::Socket();
create a socket

$sock->bind('127.0.0.1', '8080');
$sock->listen(10);

bind the socket on the local port 8080 and set the queue length

while ( my $socket = $sock->accept(10) ) {
waiting for connection
when connected to $ socket, the new LWP :: Socket () will return

$socket->read( \$file_name );
$file_name =~ s/GET \/([^ ]*) HTTP.+/$1/s;

read everything from the socket and fetch the name of the requested file

$socket->write( $headers . $content );
write the headers and the answer to the socket

$socket->shutdown();
close the received “per session” LWP :: Socket ()

This could even be finished, but we want to better disguise as an http-server, which means we need multi-threading in order to withstand the load :)
To do this, we use the FCGI :: ProcManager module, as a result we will have one “head” process and five children. To do this, add four lines:
  1. # ...
  2. use LWP :: Socket ;
  3. use FCGI :: ProcManager qw / pm_manage pm_pre_dispatch pm_post_dispatch / ;
  4. my $ headers = "HTTP / 1.1 200 OK \ r \ n Content-Type: text / html \ r \ n \ r \ n " ;
  5. my $ sock = new LWP :: Socket ( ) ;
  6. die "Can't bind a socket" unless $ sock -> bind ( '127.0.0.1' , '8080' ) ;
  7. $ sock -> listen ( 10 ) ;
  8. pm_manage ( n_processes => 5 ) ;
  9. while ( my $ socket = $ sock -> accept ( 10 ) ) {
  10. pm_pre_dispatch ( ) ;
  11. my $ content = "Hello from Habr" ;
  12. # ...
  13. $ socket -> shutdown ( ) ;
  14. pm_post_dispatch ( ) ;
  15. }
  16. $ sock -> shutdown ( ) ;

And our “server” is ready. You can use :). It remains to give the complete code so that it can be copied into the file, run and make sure that everything works.
This code with a few additions:
- added headlines
- “greeting” is given as an index page
- as the rest - what will be asked
- if the file is not found we inform the browser about this 404 error
- added comments
  1. #! / usr / bin / perl
  2. use strict ;
  3. use warnings ;
  4. use LWP :: Socket ;
  5. use FCGI :: ProcManager qw / pm_manage pm_pre_dispatch pm_post_dispatch / ;
  6. # Prepare headers
  7. my $ headers = "HTTP / 1.1% d OK \ r \ n "
  8. . "Server: FakeServer / 2009-09-12 \ r \ n "
  9. . "Content-Type: text / html \ r \ n "
  10. . "Content-Length:% d \ r \ n "
  11. . "Connection: close \ r \ n \ r \ n " ;
  12. # Prepare and open socket
  13. my $ sock = new LWP :: Socket ( ) ;
  14. die "Can't bind a socket" unless $ sock -> bind ( '127.0.0.1' , '8080' ) ;
  15. $ sock -> listen ( 10 ) ;
  16. # Create 5 childs
  17. pm_manage ( n_processes => 5 ) ;
  18. # Accepts a new connection
  19. while ( my $ socket = $ sock -> accept ( 10 ) ) {
  20. # Passing direction to child
  21. pm_pre_dispatch ( ) ;
  22. # Default content
  23. my $ content = "<html> <body> <h1> Hello from Habr </ h1> </ body> </ html>" ;
  24. my $ stat = 200 ;
  25. my $ file_name ;
  26. # Read from socket
  27. $ socket -> read ( \ $ file_name ) ;
  28. # Get wanted file name
  29. $ file_name = ~ s / GET \ / ([^] *) HTTP. + / $ 1 / s ;
  30. if ( $ file_name ) {
  31. if ( - f $ file_name and open FILE , '<' , $ file_name ) {
  32. # Read from file
  33. $ content = join "" , <FILE> ;
  34. close FILE ;
  35. }
  36. else {
  37. $ content = "File not found" ;
  38. $ stat = 404 ;
  39. }
  40. }
  41. # Puts headers and content into the socket
  42. $ socket -> write ( sprintf ( $ headers , $ stat , length $ content ) ) ;
  43. $ socket -> write ( $ content ) ;
  44. $ socket -> shutdown ( ) ;
  45. # Child's work complete
  46. pm_post_dispatch ( ) ;
  47. }
  48. # Close socket
  49. $ sock -> shutdown ( ) ;


Instructions for use:
- copy code and paste into file
- run (perl file.pl)
- in the browser open https: //127.0.0.1: 8080 /

I hope the last option will not scare anyone :)

PS If anyone is interested, then this case with 10 children ate 15 meters of RAM, while testing with requests in 30 threads, it managed to process about 2000 requests per second (10 threads requested existing files). Launched on the local machine, on the test, both cores were loaded to the ceiling.

---------
Backlight from here

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


All Articles