responseheaders.inc 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. Now that we are able to inspect the incoming request in great detail,
  2. this chapter discusses the means to enrich the outgoing responses likewise.
  3. As you have learned in the @emph{Hello, Browser} chapter, some obligatory
  4. header fields are added and set automatically for simple responses by the library
  5. itself but if more advanced features are desired, additional fields have to be created.
  6. One of the possible fields is the content type field and an example will be developed around it.
  7. This will lead to an application capable of correctly serving different types of files.
  8. When we responded with HTML page packed in the static string previously, the client had no choice
  9. but guessing about how to handle the response, because the server had not told him.
  10. What if we had sent a picture or a sound file? Would the message have been understood
  11. or merely been displayed as an endless stream of random characters in the browser?
  12. This is what the mime content types are for. The header of the response is extended
  13. by certain information about how the data is to be interpreted.
  14. To introduce the concept, a picture of the format @emph{PNG} will be sent to the client
  15. and labeled accordingly with @code{image/png}.
  16. Once again, we can base the new example on the @code{hellobrowser} program.
  17. @verbatim
  18. #define FILENAME "picture.png"
  19. #define MIMETYPE "image/png"
  20. static int
  21. answer_to_connection (void *cls, struct MHD_Connection *connection,
  22. const char *url,
  23. const char *method, const char *version,
  24. const char *upload_data,
  25. size_t *upload_data_size, void **req_cls)
  26. {
  27. unsigned char *buffer = NULL;
  28. struct MHD_Response *response;
  29. @end verbatim
  30. @noindent
  31. We want the program to open the file for reading and determine its size:
  32. @verbatim
  33. int fd;
  34. int ret;
  35. struct stat sbuf;
  36. if (0 != strcmp (method, "GET"))
  37. return MHD_NO;
  38. if ( (-1 == (fd = open (FILENAME, O_RDONLY))) ||
  39. (0 != fstat (fd, &sbuf)) )
  40. {
  41. /* error accessing file */
  42. /* ... (see below) */
  43. }
  44. /* ... (see below) */
  45. @end verbatim
  46. @noindent
  47. When dealing with files, there is a lot that could go wrong on the
  48. server side and if so, the client should be informed with @code{MHD_HTTP_INTERNAL_SERVER_ERROR}.
  49. @verbatim
  50. /* error accessing file */
  51. if (fd != -1) close (fd);
  52. const char *errorstr =
  53. "<html><body>An internal server error has occurred!\
  54. </body></html>";
  55. response =
  56. MHD_create_response_from_buffer (strlen (errorstr),
  57. (void *) errorstr,
  58. MHD_RESPMEM_PERSISTENT);
  59. if (response)
  60. {
  61. ret =
  62. MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
  63. response);
  64. MHD_destroy_response (response);
  65. return MHD_YES;
  66. }
  67. else
  68. return MHD_NO;
  69. if (!ret)
  70. {
  71. const char *errorstr = "<html><body>An internal server error has occurred!\
  72. </body></html>";
  73. if (buffer) free(buffer);
  74. response = MHD_create_response_from_buffer (strlen(errorstr), (void*) errorstr,
  75. MHD_RESPMEM_PERSISTENT);
  76. if (response)
  77. {
  78. ret = MHD_queue_response (connection,
  79. MHD_HTTP_INTERNAL_SERVER_ERROR,
  80. response);
  81. MHD_destroy_response (response);
  82. return MHD_YES;
  83. }
  84. else return MHD_NO;
  85. }
  86. @end verbatim
  87. @noindent
  88. Note that we nevertheless have to create a response object even for sending a simple error code.
  89. Otherwise, the connection would just be closed without comment, leaving the client curious about
  90. what has happened.
  91. But in the case of success a response will be constructed directly from the file descriptor:
  92. @verbatim
  93. /* error accessing file */
  94. /* ... (see above) */
  95. }
  96. response =
  97. MHD_create_response_from_fd_at_offset (sbuf.st_size, fd, 0);
  98. MHD_add_response_header (response, "Content-Type", MIMETYPE);
  99. ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
  100. MHD_destroy_response (response);
  101. @end verbatim
  102. @noindent
  103. Note that the response object will take care of closing the file descriptor for us.
  104. Up to this point, there was little new. The actual novelty is that we enhance the header with the
  105. meta data about the content. Aware of the field's name we want to add, it is as easy as that:
  106. @verbatim
  107. MHD_add_response_header(response, "Content-Type", MIMETYPE);
  108. @end verbatim
  109. @noindent
  110. We do not have to append a colon expected by the protocol behind the first
  111. field---@emph{GNU libhttpdmicro} will take care of this.
  112. The function finishes with the well-known lines
  113. @verbatim
  114. ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
  115. MHD_destroy_response (response);
  116. return ret;
  117. }
  118. @end verbatim
  119. @noindent
  120. The complete program @code{responseheaders.c} is in the @code{examples} section as usual.
  121. Find a @emph{PNG} file you like and save it to the directory the example is run from under the name
  122. @code{picture.png}. You should find the image displayed on your browser if everything worked well.
  123. @heading Remarks
  124. The include file of the @emph{MHD} library comes with the header types mentioned in @emph{RFC 2616}
  125. already defined as macros. Thus, we could have written @code{MHD_HTTP_HEADER_CONTENT_TYPE} instead
  126. of @code{"Content-Type"} as well. However, one is not limited to these standard headers and could
  127. add custom response headers without violating the protocol. Whether, and how, the client would react
  128. to these custom header is up to the receiver. Likewise, the client is allowed to send custom request
  129. headers to the server as well, opening up yet more possibilities how client and server could
  130. communicate with each other.
  131. The method of creating the response from a file on disk only works for static content.
  132. Serving dynamically created responses will be a topic of a future chapter.
  133. @heading Exercises
  134. @itemize @bullet
  135. @item
  136. Remember that the original program was written under a few assumptions---a static response
  137. using a local file being one of them. In order to simulate a very large or hard to reach file that cannot be provided
  138. instantly, postpone the queuing in the callback with the @code{sleep} function for 30 seconds
  139. @emph{if} the file @code{/big.png} is requested (but deliver the same as above). A request for
  140. @code{/picture.png} should provide just the same but without any artificial delays.
  141. Now start two instances of your browser (or even use two machines) and see how the second client
  142. is put on hold while the first waits for his request on the slow file to be fulfilled.
  143. Finally, change the sourcecode to use @code{MHD_USE_THREAD_PER_CONNECTION} when the daemon is
  144. started and try again.
  145. @item
  146. Did you succeed in implementing the clock exercise yet? This time, let the server save the
  147. program's start time @code{t} and implement a response simulating a countdown that reaches 0 at
  148. @code{t+60}. Returning a message saying on which point the countdown is, the response should
  149. ultimately be to reply "Done" if the program has been running long enough,
  150. An unofficial, but widely understood, response header line is @code{Refresh: DELAY; url=URL} with
  151. the uppercase words substituted to tell the client it should request the given resource after
  152. the given delay again. Improve your program in that the browser (any modern browser should work)
  153. automatically reconnects and asks for the status again every 5 seconds or so. The URL would have
  154. to be composed so that it begins with "http://", followed by the @emph{URI} the server is reachable
  155. from the client's point of view.
  156. Maybe you want also to visualize the countdown as a status bar by creating a
  157. @code{<table>} consisting of one row and @code{n} columns whose fields contain small images of either
  158. a red or a green light.
  159. @end itemize