I quite like the code, there isn't much to say. There are small improvements to make - better command line arguments handling, and perhaps graceful handling of EOF (and possibly other exceptions) in the server code, but as it is it works also just fine.
Perhaps the only more important comment is that your server is vulnerable to a memory exhaustion attack, if an adversary passes extremely long line.
The most interesting part is how to make the server code extensible. You very nicely pass the server function String -> String
as a parameter to the server implementation. But it transforms lines one by one, so it won't be possible to implement more complex interaction.
So the next step would be to apply a String -> String
function to the whole input, in the spirit of interact
(IIRC this is how early functional languages defined their input/output, before monads and the IO
monad were invented). But this has drawbacks too:
- Either you read the whole input and then produce the whole output, which is rather limiting.
- Or you use lazy IO, which is very tricky and problematic tricky and problematic. I certainly don't recommend it.
- The server code can't do anything except computing output from input.
The last point can be solved by extending the type to String -> IO String
. But still doesn't solve the lazy IO problem.
One step further would be to define a data type that'd consume a piece of output, produce a piece of input, and a continuation to proceed further:
data Application = String -> IO (String, Maybe Application)
This works nicely for applications that have well defined input/output tokes, such as interacting line-by-line. But for more complex operations, it's still not sufficient. In particular, sometimes you need to consume several input chunks until producing an output one, or vice versa. For example imagine you need to implement splitting input into lines by yourself - you need to read possibly multiple (or no) chunks until you encounter \n
.
The solution to this are several libraries that more or less do the same thing: iteratees, conduit, pipes iteratees, conduit, pipes (for comparison see this question this question). Conduit is used in the Yesod web server and has a lot of different libraries. In conduit-extra you have libraries for connecting a conduit to a socket, for creating conduits from a parser, splitting input into lines etc. It also works on more efficient ByteString
s, rather than String
s.
I quite like the code, there isn't much to say. There are small improvements to make - better command line arguments handling, and perhaps graceful handling of EOF (and possibly other exceptions) in the server code, but as it is it works also just fine.
Perhaps the only more important comment is that your server is vulnerable to a memory exhaustion attack, if an adversary passes extremely long line.
The most interesting part is how to make the server code extensible. You very nicely pass the server function String -> String
as a parameter to the server implementation. But it transforms lines one by one, so it won't be possible to implement more complex interaction.
So the next step would be to apply a String -> String
function to the whole input, in the spirit of interact
(IIRC this is how early functional languages defined their input/output, before monads and the IO
monad were invented). But this has drawbacks too:
- Either you read the whole input and then produce the whole output, which is rather limiting.
- Or you use lazy IO, which is very tricky and problematic. I certainly don't recommend it.
- The server code can't do anything except computing output from input.
The last point can be solved by extending the type to String -> IO String
. But still doesn't solve the lazy IO problem.
One step further would be to define a data type that'd consume a piece of output, produce a piece of input, and a continuation to proceed further:
data Application = String -> IO (String, Maybe Application)
This works nicely for applications that have well defined input/output tokes, such as interacting line-by-line. But for more complex operations, it's still not sufficient. In particular, sometimes you need to consume several input chunks until producing an output one, or vice versa. For example imagine you need to implement splitting input into lines by yourself - you need to read possibly multiple (or no) chunks until you encounter \n
.
The solution to this are several libraries that more or less do the same thing: iteratees, conduit, pipes (for comparison see this question). Conduit is used in the Yesod web server and has a lot of different libraries. In conduit-extra you have libraries for connecting a conduit to a socket, for creating conduits from a parser, splitting input into lines etc. It also works on more efficient ByteString
s, rather than String
s.
I quite like the code, there isn't much to say. There are small improvements to make - better command line arguments handling, and perhaps graceful handling of EOF (and possibly other exceptions) in the server code, but as it is it works also just fine.
Perhaps the only more important comment is that your server is vulnerable to a memory exhaustion attack, if an adversary passes extremely long line.
The most interesting part is how to make the server code extensible. You very nicely pass the server function String -> String
as a parameter to the server implementation. But it transforms lines one by one, so it won't be possible to implement more complex interaction.
So the next step would be to apply a String -> String
function to the whole input, in the spirit of interact
(IIRC this is how early functional languages defined their input/output, before monads and the IO
monad were invented). But this has drawbacks too:
- Either you read the whole input and then produce the whole output, which is rather limiting.
- Or you use lazy IO, which is very tricky and problematic. I certainly don't recommend it.
- The server code can't do anything except computing output from input.
The last point can be solved by extending the type to String -> IO String
. But still doesn't solve the lazy IO problem.
One step further would be to define a data type that'd consume a piece of output, produce a piece of input, and a continuation to proceed further:
data Application = String -> IO (String, Maybe Application)
This works nicely for applications that have well defined input/output tokes, such as interacting line-by-line. But for more complex operations, it's still not sufficient. In particular, sometimes you need to consume several input chunks until producing an output one, or vice versa. For example imagine you need to implement splitting input into lines by yourself - you need to read possibly multiple (or no) chunks until you encounter \n
.
The solution to this are several libraries that more or less do the same thing: iteratees, conduit, pipes (for comparison see this question). Conduit is used in the Yesod web server and has a lot of different libraries. In conduit-extra you have libraries for connecting a conduit to a socket, for creating conduits from a parser, splitting input into lines etc. It also works on more efficient ByteString
s, rather than String
s.
I quite like the code, there isn't much to say. There are small improvements to make - better command line arguments handling, and perhaps graceful handling of EOF (and possibly other exceptions) in the server code, but as it is it works also just fine.
Perhaps the only more important comment is that your server is vulnerable to a memory exhaustion attack, if an adversary passes extremely long line.
The most interesting part is how to make the server code extensible. You very nicely pass the server function String -> String
as a parameter to the server implementation. But it transforms lines one by one, so it won't be possible to implement more complex interaction.
So the next step would be to apply a String -> String
function to the whole input, in the spirit of interact
(IIRC this is how early functional languages defined their input/output, before monads and the IO
monad were invented). But this has drawbacks too:
- Either you read the whole input and then produce the whole output, which is rather limiting.
- Or you use lazy IO, which is very tricky and problematic. I certainly don't recommend it.
- The server code can't do anything except computing output from input.
The last point can be solved by extending the type to String -> IO String
. But still doesn't solve the lazy IO problem.
One step further would be to define a data type that'd consume a piece of output, produce a piece of input, and a continuation to proceed further:
data Application = String -> IO (String, Maybe Application)
This works nicely for applications that have well defined input/output tokes, such as interacting line-by-line. But for more complex operations, it's still not sufficient. In particular, sometimes you need to consume several input chunks until producing an output one, or vice versa. For example imagine you need to implement splitting input into lines by yourself - you need to read possibly multiple (or no) chunks until you encounter \n
.
The solution to this are several libraries that more or less do the same thing: iteratees, conduit, pipes (for comparison see this question). Conduit is used in the Yesod web server and has a lot of different libraries. In conduit-extra you have libraries for connecting a conduit to a socket, for creating conduits from a parser, splitting input into lines etc. It also works on more efficient ByteString
s, rather than String
s.