ltn012.tex 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. \documentclass[10pt]{article}
  2. \usepackage{fancyvrb}
  3. \usepackage{url}
  4. \DefineVerbatimEnvironment{lua}{Verbatim}{fontsize=\small,commandchars=\@\#\%}
  5. \DefineVerbatimEnvironment{C}{Verbatim}{fontsize=\small,commandchars=\@\#\%}
  6. \DefineVerbatimEnvironment{mime}{Verbatim}{fontsize=\small,commandchars=\$\#\%}
  7. \newcommand{\stick}[1]{\vbox{\setlength{\parskip}{0pt}#1}}
  8. \newcommand{\bl}{\ensuremath{\mathtt{\backslash}}}
  9. \newcommand{\CR}{\texttt{CR}}
  10. \newcommand{\LF}{\texttt{LF}}
  11. \newcommand{\CRLF}{\texttt{CR~LF}}
  12. \newcommand{\nil}{\texttt{nil}}
  13. \title{Filters, sources, sinks, and pumps\\
  14. {\large or Functional programming for the rest of us}}
  15. \author{Diego Nehab}
  16. \begin{document}
  17. \maketitle
  18. \begin{abstract}
  19. Certain data processing operations can be implemented in the
  20. form of filters. A filter is a function that can process
  21. data received in consecutive invocations, returning partial
  22. results each time it is called. Examples of operations that
  23. can be implemented as filters include the end-of-line
  24. normalization for text, Base64 and Quoted-Printable transfer
  25. content encodings, the breaking of text into lines, SMTP
  26. dot-stuffing, and there are many others. Filters become
  27. even more powerful when we allow them to be chained together
  28. to create composite filters. In this context, filters can be
  29. seen as the internal links in a chain of data transformations.
  30. Sources and sinks are the corresponding end points in these
  31. chains. A source is a function that produces data, chunk by
  32. chunk, and a sink is a function that takes data, chunk by
  33. chunk. Finally, pumps are procedures that actively drive
  34. data from a source to a sink, and indirectly through all
  35. intervening filters. In this article, we describe the design of an
  36. elegant interface for filters, sources, sinks, chains, and
  37. pumps, and we illustrate each step with concrete examples.
  38. \end{abstract}
  39. \section{Introduction}
  40. Within the realm of networking applications, we are often
  41. required to apply transformations to streams of data. Examples
  42. include the end-of-line normalization for text, Base64 and
  43. Quoted-Printable transfer content encodings, breaking text
  44. into lines with a maximum number of columns, SMTP
  45. dot-stuffing, \texttt{gzip} compression, HTTP chunked
  46. transfer coding, and the list goes on.
  47. Many complex tasks require a combination of two or more such
  48. transformations, and therefore a general mechanism for
  49. promoting reuse is desirable. In the process of designing
  50. \texttt{LuaSocket~2.0}, we repeatedly faced this problem.
  51. The solution we reached proved to be very general and
  52. convenient. It is based on the concepts of filters, sources,
  53. sinks, and pumps, which we introduce below.
  54. \emph{Filters} are functions that can be repeatedly invoked
  55. with chunks of input, successively returning processed
  56. chunks of output. Naturally, the result of
  57. concatenating all the output chunks must be the same as the
  58. result of applying the filter to the concatenation of all
  59. input chunks. In fancier language, filters \emph{commute}
  60. with the concatenation operator. More importantly, filters
  61. must handle input data correctly no matter how the stream
  62. has been split into chunks.
  63. A \emph{chain} is a function that transparently combines the
  64. effect of one or more filters. The interface of a chain is
  65. indistinguishable from the interface of its component
  66. filters. This allows a chained filter to be used wherever
  67. an atomic filter is accepted. In particular, chains can be
  68. themselves chained to create arbitrarily complex operations.
  69. Filters can be seen as internal nodes in a network through
  70. which data will flow, potentially being transformed many
  71. times along the way. Chains connect these nodes together.
  72. The initial and final nodes of the network are
  73. \emph{sources} and \emph{sinks}, respectively. Less
  74. abstractly, a source is a function that produces new chunks
  75. of data every time it is invoked. Conversely, sinks are
  76. functions that give a final destination to the chunks of
  77. data they receive in sucessive calls. Naturally, sources
  78. and sinks can also be chained with filters to produce
  79. filtered sources and sinks.
  80. Finally, filters, chains, sources, and sinks are all passive
  81. entities: they must be repeatedly invoked in order for
  82. anything to happen. \emph{Pumps} provide the driving force
  83. that pushes data through the network, from a source to a
  84. sink, and indirectly through all intervening filters.
  85. In the following sections, we start with a simplified
  86. interface, which we later refine. The evolution we present
  87. is not contrived: it recreates the steps we ourselves
  88. followed as we consolidated our understanding of these
  89. concepts within our application domain.
  90. \subsection{A simple example}
  91. The end-of-line normalization of text is a good
  92. example to motivate our initial filter interface.
  93. Assume we are given text in an unknown end-of-line
  94. convention (including possibly mixed conventions) out of the
  95. commonly found Unix (\LF), Mac OS (\CR), and
  96. DOS (\CRLF) conventions. We would like to be able to
  97. use the folowing code to normalize the end-of-line markers:
  98. \begin{quote}
  99. \begin{lua}
  100. @stick#
  101. local CRLF = "\013\010"
  102. local input = source.chain(source.file(io.stdin), normalize(CRLF))
  103. local output = sink.file(io.stdout)
  104. pump.all(input, output)
  105. %
  106. \end{lua}
  107. \end{quote}
  108. This program should read data from the standard input stream
  109. and normalize the end-of-line markers to the canonic
  110. \CRLF\ marker, as defined by the MIME standard.
  111. Finally, the normalized text should be sent to the standard output
  112. stream. We use a \emph{file source} that produces data from
  113. standard input, and chain it with a filter that normalizes
  114. the data. The pump then repeatedly obtains data from the
  115. source, and passes it to the \emph{file sink}, which sends
  116. it to the standard output.
  117. In the code above, the \texttt{normalize} \emph{factory} is a
  118. function that creates our normalization filter, which
  119. replaces any end-of-line marker with the canonic marker.
  120. The initial filter interface is
  121. trivial: a filter function receives a chunk of input data,
  122. and returns a chunk of processed data. When there are no
  123. more input data left, the caller notifies the filter by invoking
  124. it with a \nil\ chunk. The filter responds by returning
  125. the final chunk of processed data (which could of course be
  126. the empty string).
  127. Although the interface is extremely simple, the
  128. implementation is not so obvious. A normalization filter
  129. respecting this interface needs to keep some kind of context
  130. between calls. This is because a chunk boundary may lie between
  131. the \CR\ and \LF\ characters marking the end of a single line. This
  132. need for contextual storage motivates the use of
  133. factories: each time the factory is invoked, it returns a
  134. filter with its own context so that we can have several
  135. independent filters being used at the same time. For
  136. efficiency reasons, we must avoid the obvious solution of
  137. concatenating all the input into the context before
  138. producing any output chunks.
  139. To that end, we break the implementation into two parts:
  140. a low-level filter, and a factory of high-level filters. The
  141. low-level filter is implemented in C and does not maintain
  142. any context between function calls. The high-level filter
  143. factory, implemented in Lua, creates and returns a
  144. high-level filter that maintains whatever context the low-level
  145. filter needs, but isolates the user from its internal
  146. details. That way, we take advantage of C's efficiency to
  147. perform the hard work, and take advantage of Lua's
  148. simplicity for the bookkeeping.
  149. \subsection{The Lua part of the filter}
  150. Below is the complete implementation of the factory of high-level
  151. end-of-line normalization filters:
  152. \begin{quote}
  153. \begin{lua}
  154. @stick#
  155. function filter.cycle(lowlevel, context, extra)
  156. return function(chunk)
  157. local ret
  158. ret, context = lowlevel(context, chunk, extra)
  159. return ret
  160. end
  161. end
  162. %
  163. @stick#
  164. function normalize(marker)
  165. return filter.cycle(eol, 0, marker)
  166. end
  167. %
  168. \end{lua}
  169. \end{quote}
  170. The \texttt{normalize} factory simply calls a more generic
  171. factory, the \texttt{cycle}~factory, passing the low-level
  172. filter~\texttt{eol}. The \texttt{cycle}~factory receives a
  173. low-level filter, an initial context, and an extra
  174. parameter, and returns a new high-level filter. Each time
  175. the high-level filer is passed a new chunk, it invokes the
  176. low-level filter with the previous context, the new chunk,
  177. and the extra argument. It is the low-level filter that
  178. does all the work, producing the chunk of processed data and
  179. a new context. The high-level filter then replaces its
  180. internal context, and returns the processed chunk of data to
  181. the user. Notice that we take advantage of Lua's lexical
  182. scoping to store the context in a closure between function
  183. calls.
  184. \subsection{The C part of the filter}
  185. As for the low-level filter, we must first accept
  186. that there is no perfect solution to the end-of-line marker
  187. normalization problem. The difficulty comes from an
  188. inherent ambiguity in the definition of empty lines within
  189. mixed input. However, the following solution works well for
  190. any consistent input, as well as for non-empty lines in
  191. mixed input. It also does a reasonable job with empty lines
  192. and serves as a good example of how to implement a low-level
  193. filter.
  194. The idea is to consider both \CR\ and~\LF\ as end-of-line
  195. \emph{candidates}. We issue a single break if any candidate
  196. is seen alone, or if it is followed by a different
  197. candidate. In other words, \CR~\CR~and \LF~\LF\ each issue
  198. two end-of-line markers, whereas \CR~\LF~and \LF~\CR\ issue
  199. only one marker each. It is easy to see that this method
  200. correctly handles the most common end-of-line conventions.
  201. With this in mind, we divide the low-level filter into two
  202. simple functions. The inner function~\texttt{pushchar} performs the
  203. normalization itself. It takes each input character in turn,
  204. deciding what to output and how to modify the context. The
  205. context tells if the last processed character was an
  206. end-of-line candidate, and if so, which candidate it was.
  207. For efficiency, we use Lua's auxiliary library's buffer
  208. interface:
  209. \begin{quote}
  210. \begin{C}
  211. @stick#
  212. @#define candidate(c) (c == CR || c == LF)
  213. static int pushchar(int c, int last, const char *marker,
  214. luaL_Buffer *buffer) {
  215. if (candidate(c)) {
  216. if (candidate(last)) {
  217. if (c == last)
  218. luaL_addstring(buffer, marker);
  219. return 0;
  220. } else {
  221. luaL_addstring(buffer, marker);
  222. return c;
  223. }
  224. } else {
  225. luaL_pushchar(buffer, c);
  226. return 0;
  227. }
  228. }
  229. %
  230. \end{C}
  231. \end{quote}
  232. The outer function~\texttt{eol} simply interfaces with Lua.
  233. It receives the context and input chunk (as well as an
  234. optional custom end-of-line marker), and returns the
  235. transformed output chunk and the new context.
  236. Notice that if the input chunk is \nil, the operation
  237. is considered to be finished. In that case, the loop will
  238. not execute a single time and the context is reset to the
  239. initial state. This allows the filter to be reused many
  240. times:
  241. \begin{quote}
  242. \begin{C}
  243. @stick#
  244. static int eol(lua_State *L) {
  245. int context = luaL_checkint(L, 1);
  246. size_t isize = 0;
  247. const char *input = luaL_optlstring(L, 2, NULL, &isize);
  248. const char *last = input + isize;
  249. const char *marker = luaL_optstring(L, 3, CRLF);
  250. luaL_Buffer buffer;
  251. luaL_buffinit(L, &buffer);
  252. if (!input) {
  253. lua_pushnil(L);
  254. lua_pushnumber(L, 0);
  255. return 2;
  256. }
  257. while (input < last)
  258. context = pushchar(*input++, context, marker, &buffer);
  259. luaL_pushresult(&buffer);
  260. lua_pushnumber(L, context);
  261. return 2;
  262. }
  263. %
  264. \end{C}
  265. \end{quote}
  266. When designing filters, the challenging part is usually
  267. deciding what to store in the context. For line breaking, for
  268. instance, it could be the number of bytes that still fit in the
  269. current line. For Base64 encoding, it could be a string
  270. with the bytes that remain after the division of the input
  271. into 3-byte atoms. The MIME module in the \texttt{LuaSocket}
  272. distribution has many other examples.
  273. \section{Filter chains}
  274. Chains greatly increase the power of filters. For example,
  275. according to the standard for Quoted-Printable encoding,
  276. text should be normalized to a canonic end-of-line marker
  277. prior to encoding. After encoding, the resulting text must
  278. be broken into lines of no more than 76 characters, with the
  279. use of soft line breaks (a line terminated by the \texttt{=}
  280. sign). To help specifying complex transformations like
  281. this, we define a chain factory that creates a composite
  282. filter from one or more filters. A chained filter passes
  283. data through all its components, and can be used wherever a
  284. primitive filter is accepted.
  285. The chaining factory is very simple. The auxiliary
  286. function~\texttt{chainpair} chains two filters together,
  287. taking special care if the chunk is the last. This is
  288. because the final \nil\ chunk notification has to be
  289. pushed through both filters in turn:
  290. \begin{quote}
  291. \begin{lua}
  292. @stick#
  293. local function chainpair(f1, f2)
  294. return function(chunk)
  295. local ret = f2(f1(chunk))
  296. if chunk then return ret
  297. else return ret .. f2() end
  298. end
  299. end
  300. %
  301. @stick#
  302. function filter.chain(...)
  303. local f = select(1, ...)
  304. for i = 2, select('@#', ...) do
  305. f = chainpair(f, select(i, ...))
  306. end
  307. return f
  308. end
  309. %
  310. \end{lua}
  311. \end{quote}
  312. Thanks to the chain factory, we can
  313. define the Quoted-Printable conversion as such:
  314. \begin{quote}
  315. \begin{lua}
  316. @stick#
  317. local qp = filter.chain(normalize(CRLF), encode("quoted-printable"),
  318. wrap("quoted-printable"))
  319. local input = source.chain(source.file(io.stdin), qp)
  320. local output = sink.file(io.stdout)
  321. pump.all(input, output)
  322. %
  323. \end{lua}
  324. \end{quote}
  325. \section{Sources, sinks, and pumps}
  326. The filters we introduced so far act as the internal nodes
  327. in a network of transformations. Information flows from node
  328. to node (or rather from one filter to the next) and is
  329. transformed along the way. Chaining filters together is our
  330. way to connect nodes in this network. As the starting point
  331. for the network, we need a source node that produces the
  332. data. In the end of the network, we need a sink node that
  333. gives a final destination to the data.
  334. \subsection{Sources}
  335. A source returns the next chunk of data each time it is
  336. invoked. When there is no more data, it simply returns~\nil.
  337. In the event of an error, the source can inform the
  338. caller by returning \nil\ followed by the error message.
  339. Below are two simple source factories. The \texttt{empty} source
  340. returns no data, possibly returning an associated error
  341. message. The \texttt{file} source yields the contents of a file
  342. in a chunk by chunk fashion:
  343. \begin{quote}
  344. \begin{lua}
  345. @stick#
  346. function source.empty(err)
  347. return function()
  348. return nil, err
  349. end
  350. end
  351. %
  352. @stick#
  353. function source.file(handle, io_err)
  354. if handle then
  355. return function()
  356. local chunk = handle:read(2048)
  357. if not chunk then handle:close() end
  358. return chunk
  359. end
  360. else return source.empty(io_err or "unable to open file") end
  361. end
  362. %
  363. \end{lua}
  364. \end{quote}
  365. \subsection{Filtered sources}
  366. A filtered source passes its data through the
  367. associated filter before returning it to the caller.
  368. Filtered sources are useful when working with
  369. functions that get their input data from a source (such as
  370. the pumps in our examples). By chaining a source with one or
  371. more filters, such functions can be transparently provided
  372. with filtered data, with no need to change their interfaces.
  373. Here is a factory that does the job:
  374. \begin{quote}
  375. \begin{lua}
  376. @stick#
  377. function source.chain(src, f)
  378. return function()
  379. if not src then
  380. return nil
  381. end
  382. local chunk, err = src()
  383. if not chunk then
  384. src = nil
  385. return f(nil)
  386. else
  387. return f(chunk)
  388. end
  389. end
  390. end
  391. %
  392. \end{lua}
  393. \end{quote}
  394. \subsection{Sinks}
  395. Just as we defined an interface for a source of data, we can
  396. also define an interface for a data destination. We call
  397. any function respecting this interface a sink. In our first
  398. example, we used a file sink connected to the standard
  399. output.
  400. Sinks receive consecutive chunks of data, until the end of
  401. data is signaled by a \nil\ input chunk. A sink can be
  402. notified of an error with an optional extra argument that
  403. contains the error message, following a \nil\ chunk.
  404. If a sink detects an error itself, and
  405. wishes not to be called again, it can return \nil,
  406. followed by an error message. A return value that
  407. is not \nil\ means the sink will accept more data.
  408. Below are two useful sink factories.
  409. The table factory creates a sink that stores
  410. individual chunks into an array. The data can later be
  411. efficiently concatenated into a single string with Lua's
  412. \texttt{table.concat} library function. The \texttt{null} sink
  413. simply discards the chunks it receives:
  414. \begin{quote}
  415. \begin{lua}
  416. @stick#
  417. function sink.table(t)
  418. t = t or {}
  419. local f = function(chunk, err)
  420. if chunk then table.insert(t, chunk) end
  421. return 1
  422. end
  423. return f, t
  424. end
  425. %
  426. @stick#
  427. local function null()
  428. return 1
  429. end
  430. function sink.null()
  431. return null
  432. end
  433. %
  434. \end{lua}
  435. \end{quote}
  436. Naturally, filtered sinks are just as useful as filtered
  437. sources. A filtered sink passes each chunk it receives
  438. through the associated filter before handing it down to the
  439. original sink. In the following example, we use a source
  440. that reads from the standard input. The input chunks are
  441. sent to a table sink, which has been coupled with a
  442. normalization filter. The filtered chunks are then
  443. concatenated from the output array, and finally sent to
  444. standard out:
  445. \begin{quote}
  446. \begin{lua}
  447. @stick#
  448. local input = source.file(io.stdin)
  449. local output, t = sink.table()
  450. output = sink.chain(normalize(CRLF), output)
  451. pump.all(input, output)
  452. io.write(table.concat(t))
  453. %
  454. \end{lua}
  455. \end{quote}
  456. \subsection{Pumps}
  457. Although not on purpose, our interface for sources is
  458. compatible with Lua iterators. That is, a source can be
  459. neatly used in conjunction with \texttt{for} loops. Using
  460. our file source as an iterator, we can write the following
  461. code:
  462. \begin{quote}
  463. \begin{lua}
  464. @stick#
  465. for chunk in source.file(io.stdin) do
  466. io.write(chunk)
  467. end
  468. %
  469. \end{lua}
  470. \end{quote}
  471. Loops like this will always be present because everything
  472. we designed so far is passive. Sources, sinks, filters: none
  473. of them can do anything on their own. The operation of
  474. pumping all data a source can provide into a sink is so
  475. common that it deserves its own function:
  476. \begin{quote}
  477. \begin{lua}
  478. @stick#
  479. function pump.step(src, snk)
  480. local chunk, src_err = src()
  481. local ret, snk_err = snk(chunk, src_err)
  482. if chunk and ret then return 1
  483. else return nil, src_err or snk_err end
  484. end
  485. %
  486. @stick#
  487. function pump.all(src, snk, step)
  488. step = step or pump.step
  489. while true do
  490. local ret, err = step(src, snk)
  491. if not ret then
  492. if err then return nil, err
  493. else return 1 end
  494. end
  495. end
  496. end
  497. %
  498. \end{lua}
  499. \end{quote}
  500. The \texttt{pump.step} function moves one chunk of data from
  501. the source to the sink. The \texttt{pump.all} function takes
  502. an optional \texttt{step} function and uses it to pump all the
  503. data from the source to the sink.
  504. Here is an example that uses the Base64 and the
  505. line wrapping filters from the \texttt{LuaSocket}
  506. distribution. The program reads a binary file from
  507. disk and stores it in another file, after encoding it to the
  508. Base64 transfer content encoding:
  509. \begin{quote}
  510. \begin{lua}
  511. @stick#
  512. local input = source.chain(
  513. source.file(io.open("input.bin", "rb")),
  514. encode("base64"))
  515. local output = sink.chain(
  516. wrap(76),
  517. sink.file(io.open("output.b64", "w")))
  518. pump.all(input, output)
  519. %
  520. \end{lua}
  521. \end{quote}
  522. The way we split the filters here is not intuitive, on
  523. purpose. Alternatively, we could have chained the Base64
  524. encode filter and the line-wrap filter together, and then
  525. chain the resulting filter with either the file source or
  526. the file sink. It doesn't really matter.
  527. \section{Exploding filters}
  528. Our current filter interface has one serious shortcoming.
  529. Consider for example a \texttt{gzip} decompression filter.
  530. During decompression, a small input chunk can be exploded
  531. into a huge amount of data. To address this problem, we
  532. decided to change the filter interface and allow exploding
  533. filters to return large quantities of output data in a chunk
  534. by chunk manner.
  535. More specifically, after passing each chunk of input to
  536. a filter, and collecting the first chunk of output, the
  537. user must now loop to receive other chunks from the filter until no
  538. filtered data is left. Within these secondary calls, the
  539. caller passes an empty string to the filter. The filter
  540. responds with an empty string when it is ready for the next
  541. input chunk. In the end, after the user passes a
  542. \nil\ chunk notifying the filter that there is no
  543. more input data, the filter might still have to produce too
  544. much output data to return in a single chunk. The user has
  545. to loop again, now passing \nil\ to the filter each time,
  546. until the filter itself returns \nil\ to notify the
  547. user it is finally done.
  548. Fortunately, it is very easy to modify a filter to respect
  549. the new interface. In fact, the end-of-line translation
  550. filter we presented earlier already conforms to it. The
  551. complexity is encapsulated within the chaining functions,
  552. which must now include a loop. Since these functions only
  553. have to be written once, the user is rarely affected.
  554. Interestingly, the modifications do not have a measurable
  555. negative impact in the performance of filters that do
  556. not need the added flexibility. On the other hand, for a
  557. small price in complexity, the changes make exploding
  558. filters practical.
  559. \section{A complex example}
  560. The LTN12 module in the \texttt{LuaSocket} distribution
  561. implements all the ideas we have described. The MIME
  562. and SMTP modules are tightly integrated with LTN12,
  563. and can be used to showcase the expressive power of filters,
  564. sources, sinks, and pumps. Below is an example
  565. of how a user would proceed to define and send a
  566. multipart message, with attachments, using \texttt{LuaSocket}:
  567. \begin{quote}
  568. \begin{mime}
  569. local smtp = require"socket.smtp"
  570. local mime = require"mime"
  571. local ltn12 = require"ltn12"
  572. local message = smtp.message{
  573. headers = {
  574. from = "Sicrano <[email protected]>",
  575. to = "Fulano <[email protected]>",
  576. subject = "A message with an attachment"},
  577. body = {
  578. preamble = "Hope you can see the attachment" .. CRLF,
  579. [1] = {
  580. body = "Here is our logo" .. CRLF},
  581. [2] = {
  582. headers = {
  583. ["content-type"] = 'image/png; name="luasocket.png"',
  584. ["content-disposition"] =
  585. 'attachment; filename="luasocket.png"',
  586. ["content-description"] = 'LuaSocket logo',
  587. ["content-transfer-encoding"] = "BASE64"},
  588. body = ltn12.source.chain(
  589. ltn12.source.file(io.open("luasocket.png", "rb")),
  590. ltn12.filter.chain(
  591. mime.encode("base64"),
  592. mime.wrap()))}}}
  593. assert(smtp.send{
  594. rcpt = "<[email protected]>",
  595. from = "<[email protected]>",
  596. source = message})
  597. \end{mime}
  598. \end{quote}
  599. The \texttt{smtp.message} function receives a table
  600. describing the message, and returns a source. The
  601. \texttt{smtp.send} function takes this source, chains it with the
  602. SMTP dot-stuffing filter, connects a socket sink
  603. with the server, and simply pumps the data. The message is never
  604. assembled in memory. Everything is produced on demand,
  605. transformed in small pieces, and sent to the server in chunks,
  606. including the file attachment which is loaded from disk and
  607. encoded on the fly. It just works.
  608. \section{Conclusions}
  609. In this article, we introduced the concepts of filters,
  610. sources, sinks, and pumps to the Lua language. These are
  611. useful tools for stream processing in general. Sources provide
  612. a simple abstraction for data acquisition. Sinks provide an
  613. abstraction for final data destinations. Filters define an
  614. interface for data transformations. The chaining of
  615. filters, sources and sinks provides an elegant way to create
  616. arbitrarily complex data transformations from simpler
  617. components. Pumps simply push the data through.
  618. \section{Acknowledgements}
  619. The concepts described in this text are the result of long
  620. discussions with David Burgess. A version of this text has
  621. been released on-line as the Lua Technical Note 012, hence
  622. the name of the corresponding LuaSocket module, LTN12. Wim
  623. Couwenberg contributed to the implementation of the module,
  624. and Adrian Sietsma was the first to notice the
  625. correspondence between sources and Lua iterators.
  626. \end{document}