Add info to readme
This commit is contained in:
parent
565caf5fa6
commit
c220c2ec78
|
@ -4,7 +4,7 @@ Fortran does not have any native way to connect to the internet. It cannot even
|
||||||
|
|
||||||
This makes it an absolute nightmare to create a web server since you need to import sockets and tcp libraries from C, using the using the [use iso_c_binding feature](https://fortranwiki.org/fortran/show/iso_c_binding).
|
This makes it an absolute nightmare to create a web server since you need to import sockets and tcp libraries from C, using the using the [use iso_c_binding feature](https://fortranwiki.org/fortran/show/iso_c_binding).
|
||||||
|
|
||||||
However, in the spirit of the challenge, I wanted to not have to resort to importing functions from another programming language as much as possible. Later when I create the client I would *have* to import libraries from C, but for this I could try and use "Fortran Only".
|
However, in the spirit of the challenge, I wanted to not have to resort to importing functions from another programming language as much as possible. Later when I create the client I would _have_ to import libraries from C, but for this I could try and use "Fortran Only".
|
||||||
|
|
||||||
While reading this [old Fortran google groups question from 2009](https://groups.google.com/g/comp.lang.fortran/c/wL1xdnB1plk) the group suggested that the user just use [netcat](https://en.wikipedia.org/wiki/Netcat) and serve the Fortran program as if it were a cgi script.
|
While reading this [old Fortran google groups question from 2009](https://groups.google.com/g/comp.lang.fortran/c/wL1xdnB1plk) the group suggested that the user just use [netcat](https://en.wikipedia.org/wiki/Netcat) and serve the Fortran program as if it were a cgi script.
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ The downside of using `netcat` is that it uses unidirectional sockets. You would
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkfifo ncpipe
|
mkfifo ncpipe
|
||||||
nc -l 8080 0<ncpipe | ./fortran-µhttpd '../../common/html/index.html' 1>ncpipe
|
nc -l 8080 0<ncpipe | ./fortran-micro-httpd '../../common/html/index.html' 1>ncpipe
|
||||||
```
|
```
|
||||||
|
|
||||||
It just doesnt seem all that clean to me. Additionally, it refused to stay open after running one message from the web client, so I gave up on that approach.
|
It just doesnt seem all that clean to me. Additionally, it refused to stay open after running one message from the web client, so I gave up on that approach.
|
||||||
|
@ -22,8 +22,9 @@ It just doesnt seem all that clean to me. Additionally, it refused to stay open
|
||||||
Somthing that does work and be a lot cleaner is [listen1](https://9fans.github.io/plan9port/man/man8/listen1.html) from [plan9port](https://9fans.github.io/plan9port/). This allows for the web browser to connect to the programs stdin, stdout, & stderr, and the program can read and write as a normal command line program.
|
Somthing that does work and be a lot cleaner is [listen1](https://9fans.github.io/plan9port/man/man8/listen1.html) from [plan9port](https://9fans.github.io/plan9port/). This allows for the web browser to connect to the programs stdin, stdout, & stderr, and the program can read and write as a normal command line program.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
listen1 'tcp!*!8080' ./fortran-µhttpd
|
listen1 'tcp!*!8080' ./fortran-micro-httpd
|
||||||
```
|
```
|
||||||
|
|
||||||
I next implemented the post handling. In www.f90, it checks to see if the header returning from the
|
I next implemented the post handling. In www.f90, it checks to see if the header returning from the
|
||||||
|
|
||||||
I found a library to interface with the sqlite library I will store the username, password, and other information about.
|
I found a library to interface with the sqlite library I will store the username, password, and other information about.
|
||||||
|
@ -36,3 +37,138 @@ The implementation does not work well with existing systems since you have to co
|
||||||
|
|
||||||
This is a horrible implementation from a security POV, since there is no easy way to sanitize the input, fortran certainly doesn't do that out of the box.
|
This is a horrible implementation from a security POV, since there is no easy way to sanitize the input, fortran certainly doesn't do that out of the box.
|
||||||
|
|
||||||
|
Also, it turned out that the library simply did not work. Perhaps it was the way I was passing in the fotran strings, but I only ever got garbage back and not a hashed value.
|
||||||
|
|
||||||
|
Speaking of strings. At first fortran luls you into a false sense of security with strings. Fortran has a fantastic way of handling "defered length arrays". Which means that you can take an array of unknown size and just keep appending arrays to each other.
|
||||||
|
|
||||||
|
For example, this is totaly valid in fortran:
|
||||||
|
|
||||||
|
```fortran
|
||||||
|
character, dimension(:), allocatable :: tape
|
||||||
|
character, dimension(4) :: magic_module_header = [ achar(0), achar(97), achar(115), achar(109) ]
|
||||||
|
character, dimension(4) :: module_version = [ achar(1), achar(0), achar(0), achar(0) ]
|
||||||
|
|
||||||
|
tape = [magic_module_header]
|
||||||
|
tape = [tape, module_version]
|
||||||
|
|
||||||
|
open(unit=u, file=file_output, access='stream', status='replace', action='write', iostat=ios)
|
||||||
|
write(u, iostat=ios) tape
|
||||||
|
close(u, iostat=ios)
|
||||||
|
|
||||||
|
deallocate(tape)
|
||||||
|
```
|
||||||
|
|
||||||
|
Pretty cool for a language with similar performance to C!
|
||||||
|
|
||||||
|
Well, its not that simple when it comes to "strings" because there are different ways to structure a "string" in fortran.
|
||||||
|
|
||||||
|
```fortran
|
||||||
|
! theres the 'suggested fortran 2023 standard way'
|
||||||
|
! this format is also the way you can use a lot of the built in string functions and the '//' append operator.
|
||||||
|
character(len=:), allocatable :: string
|
||||||
|
|
||||||
|
! this way which is useful since you can use the same interface as a normal 1d array
|
||||||
|
character, dimension(:), allocatable :: string
|
||||||
|
|
||||||
|
! this other other way which only works inside of subrotines/functions
|
||||||
|
character, intent(in) :: string(:)
|
||||||
|
|
||||||
|
! also this older way
|
||||||
|
character(*) :: string
|
||||||
|
|
||||||
|
! and this ancent '77 spec way which wont work in some compiling modes.
|
||||||
|
CHARACTER string*17
|
||||||
|
```
|
||||||
|
|
||||||
|
Can you tell the difference between all of them? theyre all doing almost the same thing, but they are slightly different. And some ways seem to work fine until you try to write to output. Or if you use a speicifc operator on one tyep, you cant on the other.
|
||||||
|
|
||||||
|
This is exactly what happened with the `sqliteff` libarary I was trying to use.
|
||||||
|
|
||||||
|
Lets take a look at this chunk of fortran code
|
||||||
|
|
||||||
|
```fortran
|
||||||
|
character, dimension(:), allocatable, intent(in) :: request
|
||||||
|
integer, intent(in) :: length
|
||||||
|
|
||||||
|
character(len=24) :: username
|
||||||
|
character(len=:), allocatable :: command
|
||||||
|
|
||||||
|
get_username: do i = 1, length
|
||||||
|
if (request(i) .eq. '=') then
|
||||||
|
s_idx = i + 1
|
||||||
|
end if
|
||||||
|
|
||||||
|
if (request(i) .eq. '&') then
|
||||||
|
e_idx = i - 1
|
||||||
|
exit get_username
|
||||||
|
end if
|
||||||
|
end do get_username
|
||||||
|
username = transfer(request(s_idx:e_idx), username)
|
||||||
|
|
||||||
|
command = 'echo ' // trim(adjustl(username))
|
||||||
|
call execute_command_line(command)
|
||||||
|
```
|
||||||
|
|
||||||
|
Does this seem like it should work?
|
||||||
|
|
||||||
|
Should be that we pare through the request to find the first "username" value.
|
||||||
|
|
||||||
|
then use the transfer function which "magically" converts the slice of the request into the username string.
|
||||||
|
|
||||||
|
then we call "echo" and run it on the command line. Simple enough right?
|
||||||
|
|
||||||
|
Well no, because username is a static size, it keeps it length and will export random junk from other arrays alongside of it.
|
||||||
|
|
||||||
|
So we can call trim and adjestl and cut it down to the correct length right? actually no, once again because it is a static size it will generate a bunch of 'zero length' characters which will break the sqlite call.
|
||||||
|
|
||||||
|
So we have to come up with a compleatly different way of doing this.
|
||||||
|
|
||||||
|
The only consistant way to fix this issue is by using fortran array slices.
|
||||||
|
|
||||||
|
```fortran
|
||||||
|
character, dimension(:), allocatable, intent(in) :: request
|
||||||
|
integer, intent(in) :: length
|
||||||
|
|
||||||
|
character(len=24) :: username
|
||||||
|
character(len=:), allocatable :: command
|
||||||
|
|
||||||
|
j = 1
|
||||||
|
get_username: do i = 1, length
|
||||||
|
if (request(i) .eq. '=') then
|
||||||
|
s_idx = i + 1
|
||||||
|
start = .true.
|
||||||
|
end if
|
||||||
|
|
||||||
|
if (request(i) .eq. '&') then
|
||||||
|
e_idx = i - 1
|
||||||
|
start = .false.
|
||||||
|
exit get_username
|
||||||
|
end if
|
||||||
|
|
||||||
|
if (start) then
|
||||||
|
username(j:j) = request(i + 1)
|
||||||
|
j = j + 1
|
||||||
|
end if
|
||||||
|
end do get_username
|
||||||
|
username_len = j-2
|
||||||
|
|
||||||
|
command = 'echo ' // username(:username_len)
|
||||||
|
call execute_command_line(command)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is terrible code, but it works. We keep track of the length of the string and then we can take a slice out of the total string when we are concatinating the command. So now it works fine.
|
||||||
|
|
||||||
|
Now we have fortran string weirdness out of the way we can talk a little about the actual implementation of this.
|
||||||
|
|
||||||
|
The request/response headers are pretty easily replicated.
|
||||||
|
|
||||||
|
Probably the best feature in the HTTP protocol is actually the `Content-Length` header, since it allows you to exactly allocate the amount of memory needed to store the body of the request.
|
||||||
|
|
||||||
|
There isnt a whole lot left to implement. Just getting the path to the html file and the database file from the args.
|
||||||
|
|
||||||
|
We read the index.html from the file and serve it to the front end on a `GET` request.
|
||||||
|
|
||||||
|
On a `POST` request it will parse the body and insert the user data into the database.
|
||||||
|
|
||||||
|
This is probably the longest I have had to take for the smallest amount of actual features for anything I have created yet.
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ contains
|
||||||
write (output_unit, '(A)') "HTTP/1.1 200 OK"
|
write (output_unit, '(A)') "HTTP/1.1 200 OK"
|
||||||
write (output_unit, '(A)') "Server: fortran-micro-httpd"
|
write (output_unit, '(A)') "Server: fortran-micro-httpd"
|
||||||
write (output_unit, '(A)') "Content-Type: text/html; charset=UTF-8"
|
write (output_unit, '(A)') "Content-Type: text/html; charset=UTF-8"
|
||||||
write (output_unit, '(A)', advance='no') "Content-length: "
|
write (output_unit, '(A)', advance='no') "Content-Length: "
|
||||||
write (output_unit, *) length
|
write (output_unit, *) length
|
||||||
write (output_unit, *)
|
write (output_unit, *)
|
||||||
end subroutine response_header
|
end subroutine response_header
|
||||||
|
|
Loading…
Reference in New Issue