« index

Coverage for /Users/kris/q-io/http-apps/status.js : 97%

173 lines | 168 run | 5 missing | 0 partial | 14 blocks | 10 blocks run | 4 blocks missing

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

  var Negotiation = require("./negotiate");
  var HtmlApps = require("./html");
  
  /**
   * {Object * String} a mapping of HTTP status codes to
   * their standard descriptions.
   */
  // Every standard HTTP code mapped to the appropriate message.
  // Stolen from Rack which stole from Mongrel
  exports.statusCodes = {
      100: 'Continue',
      101: 'Switching Protocols',
      102: 'Processing',
      200: 'OK',
      201: 'Created',
      202: 'Accepted',
      203: 'Non-Authoritative Information',
      204: 'No Content',
      205: 'Reset Content',
      206: 'Partial Content',
      207: 'Multi-Status',
      300: 'Multiple Choices',
      301: 'Moved Permanently',
      302: 'Found',
      303: 'See Other',
      304: 'Not Modified',
      305: 'Use Proxy',
      307: 'Temporary Redirect',
      400: 'Bad Request',
      401: 'Unauthorized',
      402: 'Payment Required',
      403: 'Forbidden',
      404: 'Not Found',
      405: 'Method Not Allowed',
      406: 'Not Acceptable',
      407: 'Proxy Authentication Required',
      408: 'Request Timeout',
      409: 'Conflict',
      410: 'Gone',
      411: 'Length Required',
      412: 'Precondition Failed',
      413: 'Request Entity Too Large',
      414: 'Request-URI Too Large',
      415: 'Unsupported Media Type',
      416: 'Request Range Not Satisfiable',
      417: 'Expectation Failed',
      422: 'Unprocessable Entity',
      423: 'Locked',
      424: 'Failed Dependency',
      500: 'Internal Server Error',
      501: 'Not Implemented',
      502: 'Bad Gateway',
      503: 'Service Unavailable',
      504: 'Gateway Timeout',
      505: 'HTTP Version Not Supported',
      507: 'Insufficient Storage'
  };
  
  /**
   * {Object * Number} a mapping from HTTP status descriptions
   * to HTTP status codes.
   */
  exports.statusMessages = {};
  for (var code in exports.statusCodes)
      exports.statusMessages[exports.statusCodes[code]] = +code;
  
  /**
   * Determines whether an HTTP response should have a
   * response body, based on its status code.
   * @param {Number} status
   * @returns whether the HTTP response for the given status
   * code has content.
   */
  exports.statusWithNoEntityBody = function (status) {
      return (status >= 100 && status <= 199) ||
          status == 204 || status == 304;
  };
  
  /**
   * @param {Number} status
   * @returns {Function(Request) :Response} a JSGI app that returns
   * a plain text response with the given status code.
   */
  exports.appForStatus = function (status) {
      return function (request) {
          return exports.responseForStatus(request, status, request.method + " " + request.path);
      };
  };
  
  /**
   * @param {Number} status an HTTP status code
   * @param {String} message (optional) a message to include
   * in the response body.
   * @returns a JSGI HTTP response object with the given status
   * code and message as its body, if the status supports
   * a body.
   */
  exports.responseForStatus = function(request, status, addendum) {
      if (exports.statusCodes[status] === undefined)
          throw "Unknown status code";
  
      var message = exports.statusCodes[status];
  
      // RFC 2616, 10.2.5:
      // The 204 response MUST NOT include a message-body, and thus is always
      // terminated by the first empty line after the header fields.
      // RFC 2616, 10.3.5:
      // The 304 response MUST NOT contain a message-body, and thus is always
      // terminated by the first empty line after the header fields.
      if (exports.statusWithNoEntityBody(status)) {
          return {status: status, headers: {}};
      } else {
          var handlers = {};
          handlers["text/plain"] = exports.textResponseForStatus;
          if (request.handleHtmlFragmentResponse) {
              handlers["text/html"] = exports.htmlResponseForStatus;
          }
          var responseForStatus = Negotiation.negotiate(request, handlers) || exports.textResponseForStatus;
          return responseForStatus(request, status, message, addendum);
      }
  };
  
  exports.textResponseForStatus = function (request, status, message, addendum) {
      var content = message + "\n";
      if (addendum) {
          content += addendum + "\n";
      }
      var contentLength = content.length;
      return {
          status: status,
          statusMessage: message,
          headers: {
              "content-length": contentLength
          },
          body: [content]
      };
  };
  
  exports.htmlResponseForStatus = function (request, status, message, addendum) {
      return {
          status: status,
          statusMessage: message,
          headers: {},
          htmlTitle: message,
          htmlFragment: {
              forEach: function (write) {
                  write("<h1>" + HtmlApps.escapeHtml(message) + "</h1>\n");
                  write("<p>Status: " + status + "</p>\n");
                  if (addendum) {
                      write("<pre>" + HtmlApps.escapeHtml(addendum) + "</pre>\n");
                  }
              }
          }
      }
  };
  
  /**
   * {App} an application that returns a 400 response.
   */
  exports.badRequest = exports.appForStatus(400);
  /**
   * {App} an application that returns a 404 response.
   */
  exports.notFound = exports.appForStatus(404);
  /**
   * {App} an application that returns a 405 response.
   */
  exports.methodNotAllowed = exports.appForStatus(405);
  /**
   * {App} an application that returns a 405 response.
   */
  exports.noLanguage =
  exports.notAcceptable = exports.appForStatus(406);
« index | cover.io