1

i'm implementing simple http server and i want my responses depend on some global state. For example if i get request 'get_settings' from the same client for the first time i will send large settings json, and for the second time i will just send 'Not-modified' http response.

Something like that

import Network.Simple.TCP

main = withSocketsDo $ do 
    let settings_state = 0 -- flag for settings response
    serve (Host "127.0.0.1") "23980" $ \(conn_sock, remote_addr) -> do
        putStrLn $ "TCP connection established from " ++ show remote_addr
        (Just inp) <- recv conn_sock 1024
        send conn_sock (process inp settings_state)

process :: B.ByteString -> Int -> B.ByteString
process inp flag
    | flag == 0 = ... -- return full response and change global flag
    | otherwise = ... -- return 'Not-modified'

And the question is how can i implement it? And i would like to do it as simple as possible, manually, without any Monad Transformers and so on. Let the code be ugly, but simple. Thanks

Alexander
  • 779
  • 8
  • 17
  • How do you know that the client hasn't purged the cache? How do you know the client is even capable of caching? How do you identify the same user, given that remote address, cookies, and many other factors aren't reliable? You're on a great way to shoot yourself in the foot. Instead, look for the `If-Modified-*` request headers. – Zeta Jul 19 '14 at 20:14
  • I know everything i need, because it was i who wrote corresponding client in c++, and i know exactly what i need from my server. And (if u haven't noticed) this question is about haskell, not about http implementation details. – Alexander Jul 19 '14 at 20:20
  • 1
    Oh, I've noticed. I've seen your "ugly, but simple" statement, but ignored it for a while, since it wasn't clear that you were using your own client to connect to the server. And at that point, I just wanted to make sure you know what you're up to. But who am I to stand between a man and a solution? Have a look at [IORef](http://hackage.haskell.org/package/base-4.7.0.0/docs/Data-IORef.html). And depending on what you actually want to do: http://chimera.labs.oreilly.com/books/1230000000929/index.html – Zeta Jul 19 '14 at 20:31
  • 1
    Just pass whatever state you want as another argument into `process`. `MVar`, `IORef`, `TVar`, a connection to a real data base, etc. – Thomas M. DuBuisson Jul 19 '14 at 20:34
  • Wow, Zeta, you should have made this an answer. Thank you. But i can't understand how it comes with Haskell famous purity? Anyway, thanks. – Alexander Jul 19 '14 at 20:43

1 Answers1

4

Since changing the flag clearly has some side effects, the result of process will be in IO:

process :: B.ByteString -> Int -> IO B.ByteString

Since you don't want to use any kind of monad transformer, you need to exchange the Int with some mutable reference. Yes, you've read correctly: There are several types that are mutable, such as IORef, MVar, TVar, MVector, STRef …. To stay simple, lets stick to IORef.

process :: B.ByteString -> IORef Int -> IO B.ByteString
process inp flag = do
    oldFlag <- readIORef flag
    if oldFlag == 0 
       then do modifyIORef' flag (+1)
               return bigJSONObject
       else return notModified

Note that you didn't provide any logic for the flag, so I simply increased the value, but you probably want to do something else (or change the flag to IORef Bool). Note that you also want to use atomicModifyIORef' if you want to use the IORef safely in a multithreaded program:

    oldFlag <- atomicModifyIORef' flag (\o -> (o+1,o))

Either way, you need to create the IORef with newIORef value, so your code snippets becomes something like

main = withSocketsDo $ do 
    settings_state <- newIORef 0
    serve (Host "127.0.0.1") "23980" $ \(conn_sock, remote_addr) -> do
        -- ...
Zeta
  • 103,620
  • 13
  • 194
  • 236