File: Stream.hs

package info (click to toggle)
haskell-http 20060707-2
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 220 kB
  • ctags: 7
  • sloc: haskell: 1,885; sh: 108; makefile: 82
file content (175 lines) | stat: -rw-r--r-- 5,401 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
-----------------------------------------------------------------------------
-- |
-- Module      :  Network.Stream
-- Copyright   :  (c) Warrick Gray 2002, Bjorn Bringert 2003-2004, Simon Foster 2004
-- License     :  BSD
--
-- Maintainer  :  bjorn@bringert.net
-- Stability   :  experimental
-- Portability :  non-portable (not tested)
--
-- An library for creating abstract streams. Originally part of Gray's\/Bringert's
-- HTTP module.
--
-- * Changes by Simon Foster:
--      - Split module up into to sepearate Network.[Stream,TCP,HTTP] modules
--      
-----------------------------------------------------------------------------
module Network.Stream (
    -- ** Streams
    Debug,
    Stream(..),
    debugStream,
    
    -- ** Errors
    ConnError(..),
    Result,
    handleSocketError,
    bindE,
    myrecv

) where

import Control.Exception as Exception
import System.IO.Error

-- Networking
import Network (withSocketsDo)
import Network.BSD
import Network.URI
import Network.Socket

import Control.Monad (when,liftM,guard)
import System.IO

data ConnError = ErrorReset 
               | ErrorClosed
               | ErrorParse String
               | ErrorMisc String
    deriving(Show,Eq)

-- error propagating:
-- we could've used a monad, but that would lead us
-- into using the "-fglasgow-exts" compile flag.
bindE :: Either ConnError a -> (a -> Either ConnError b) -> Either ConnError b
bindE (Left e)  _ = Left e
bindE (Right v) f = f v

-- | This is the type returned by many exported network functions.
type Result a = Either ConnError   {- error  -}
                       a           {- result -}

-----------------------------------------------------------------
------------------ Gentle Art of Socket Sucking -----------------
-----------------------------------------------------------------

-- | Streams should make layering of TLS protocol easier in future,
-- they allow reading/writing to files etc for debugging,
-- they allow use of protocols other than TCP/IP
-- and they allow customisation.
--
-- Instances of this class should not trim
-- the input in any way, e.g. leave LF on line
-- endings etc. Unless that is exactly the behaviour
-- you want from your twisted instances ;)
class Stream x where 
    readLine   :: x -> IO (Result String)
    readBlock  :: x -> Int -> IO (Result String)
    writeBlock :: x -> String -> IO (Result ())
    close      :: x -> IO ()





-- Exception handler for socket operations
handleSocketError :: Socket -> Exception -> IO (Result a)
handleSocketError sk e =
    do { se <- getSocketOption sk SoError
       ; if se == 0
            then throw e
            else return $ if se == 10054       -- reset
                then Left ErrorReset
                else Left $ ErrorMisc $ show se
       }




instance Stream Socket where
    readBlock sk n = (liftM Right $ fn n) `Exception.catch` (handleSocketError sk)
        where
            fn x = do { str <- myrecv sk x
                      ; let len = length str
                      ; if len < x
                          then ( fn (x-len) >>= \more -> return (str++more) )                        
                          else return str
                      }

    -- Use of the following function is discouraged.
    -- The function reads in one character at a time, 
    -- which causes many calls to the kernel recv()
    -- hence causes many context switches.
    readLine sk = (liftM Right $ fn "") `Exception.catch` (handleSocketError sk)
            where
                fn str =
                    do { c <- myrecv sk 1 -- like eating through a straw.
                       ; if null c || c == "\n"
                           then return (reverse str++c)
                           else fn (head c:str)
                       }
    
    writeBlock sk str = (liftM Right $ fn str) `Exception.catch` (handleSocketError sk)
        where
            fn [] = return ()
            fn x  = send sk x >>= \i -> fn (drop i x)

    -- This slams closed the connection (which is considered rude for TCP\/IP)
    close sk = shutdown sk ShutdownBoth >> sClose sk

myrecv :: Socket -> Int -> IO String
myrecv sock len =
    let handler e = if isEOFError e then return [] else ioError e
        in System.IO.Error.catch (recv sock len) handler

-- | Allows stream logging.
-- Refer to 'debugStream' below.
data Debug x = Dbg Handle x


instance (Stream x) => Stream (Debug x) where
    readBlock (Dbg h c) n =
        do { val <- readBlock c n
           ; hPutStrLn h ("readBlock " ++ show n ++ ' ' : show val)
           ; return val
           }

    readLine (Dbg h c) =
        do { val <- readLine c
           ; hPutStrLn h ("readLine " ++ show val)
           ; return val
           }

    writeBlock (Dbg h c) str =
        do { val <- writeBlock c str
           ; hPutStrLn h ("writeBlock " ++ show val ++ ' ' : show str)
           ; return val
           }

    close (Dbg h c) =
        do { hPutStrLn h "closing..."
           ; hFlush h
           ; close c
           ; hPutStrLn h "...closed"
           ; hClose h
           }


-- | Wraps a stream with logging I\/O, the first
-- argument is a filename which is opened in AppendMode.
debugStream :: (Stream a) => String -> a -> IO (Debug a)
debugStream file stm = 
    do { h <- openFile file AppendMode
       ; hPutStrLn h "File opened for appending."
       ; return (Dbg h stm)
       }