001 package com.nimbusds.oauth2.sdk.http; 002 003 004 import java.io.IOException; 005 import java.io.PrintWriter; 006 007 import java.net.URL; 008 009 import javax.servlet.http.HttpServletResponse; 010 011 import net.jcip.annotations.ThreadSafe; 012 013 import net.minidev.json.JSONObject; 014 015 import com.nimbusds.jwt.JWT; 016 import com.nimbusds.jwt.JWTParser; 017 018 import com.nimbusds.oauth2.sdk.ParseException; 019 020 import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 021 022 023 /** 024 * HTTP response with support for the parameters required to construct an 025 * {@link com.nimbusds.oauth2.sdk.Response OAuth 2.0 response message}. This 026 * class is thread-safe. 027 * 028 * <p>Provided HTTP status code constants: 029 * 030 * <ul> 031 * <li>{@link #SC_OK HTTP 200 OK} 032 * <li>{@link #SC_FOUND HTTP 302 Redirect} 033 * <li>{@link #SC_BAD_REQUEST HTTP 400 Bad request} 034 * <li>{@link #SC_UNAUTHORIZED HTTP 401 Unauthorized} 035 * <li>{@link #SC_FORBIDDEN HTTP 403 Forbidden} 036 * </ul> 037 * 038 * <p>Supported response headers: 039 * 040 * <ul> 041 * <li>Location 042 * <li>Content-Type 043 * <li>Cache-Control 044 * <li>Pragma 045 * <li>Www-Authenticate 046 * </ul> 047 * 048 * @author Vladimir Dzhuvinov 049 * @version $version$ (2013-05-10) 050 */ 051 @ThreadSafe 052 public class HTTPResponse extends HTTPMessage { 053 054 055 /** 056 * HTTP status code (200) indicating the request succeeded normally. 057 */ 058 public static final int SC_OK = 200; 059 060 061 /** 062 * HTTP status code (302) indicating that the resource reside 063 * temporarily under a different URI (redirect). 064 */ 065 public static final int SC_FOUND = 302; 066 067 068 /** 069 * HTTP status code (400) indicating a bad request. 070 */ 071 public static final int SC_BAD_REQUEST = 400; 072 073 074 /** 075 * HTTP status code (401) indicating that the request requires HTTP 076 * authentication. 077 */ 078 public static final int SC_UNAUTHORIZED = 401; 079 080 081 /** 082 * HTTP status code (403) indicating that access to the resource was 083 * forbidden. 084 */ 085 public static final int SC_FORBIDDEN = 403; 086 087 088 /** 089 * The HTTP status code. 090 */ 091 private final int statusCode; 092 093 094 /** 095 * Specifies a {@code Location} header value (for redirects). 096 */ 097 private URL location = null; 098 099 100 /** 101 * Specifies a {@code Cache-Control} header value. 102 */ 103 private String cacheControl = null; 104 105 106 /** 107 * Specifies a {@code Pragma} header value. 108 */ 109 private String pragma = null; 110 111 112 /** 113 * Specifies a {@code Www-Authenticate} header value. 114 */ 115 private String wwwAuthenticate = null; 116 117 118 /** 119 * The raw response content. 120 */ 121 private String content = null; 122 123 124 /** 125 * Creates a new minimal HTTP response with the specified status code. 126 * 127 * @param statusCode The HTTP status code. 128 */ 129 public HTTPResponse(final int statusCode) { 130 131 this.statusCode = statusCode; 132 } 133 134 135 /** 136 * Gets the HTTP status code. 137 * 138 * @return The HTTP status code. 139 */ 140 public int getStatusCode() { 141 142 return statusCode; 143 } 144 145 146 /** 147 * Ensures this HTTP response has the specified {@link #getStatusCode 148 * status code}. 149 * 150 * @param statusCode The expected status code. 151 * 152 * @throws ParseException If the status code of this HTTP response 153 * doesn't match the expected. 154 */ 155 public void ensureStatusCode(final int statusCode) 156 throws ParseException { 157 158 if (this.statusCode != statusCode) 159 throw new ParseException("Unexpected HTTP status code, must be " + statusCode); 160 } 161 162 163 /** 164 * Ensures this HTTP response does not have a {@link #SC_OK 200 OK} 165 * status code. 166 * 167 * @throws ParseException If the status code of this HTTP response is 168 * 200 OK. 169 */ 170 public void ensureStatusCodeNotOK() 171 throws ParseException { 172 173 if (statusCode == SC_OK) 174 throw new ParseException("Unexpected HTTP status code, must not be 200 (OK)"); 175 } 176 177 178 /** 179 * Gets the {@code Location} header value (for redirects). 180 * 181 * @return The header value, {@code null} if not specified. 182 */ 183 public URL getLocation() { 184 185 return location; 186 } 187 188 189 /** 190 * Sets the {@code Location} header value (for redirects). 191 * 192 * @param location The header value, {@code null} if not specified. 193 */ 194 public void setLocation(final URL location) { 195 196 this.location = location; 197 } 198 199 200 /** 201 * Gets the {@code Cache-Control} header value. 202 * 203 * @return The header value, {@code null} if not specified. 204 */ 205 public String getCacheControl() { 206 207 return cacheControl; 208 } 209 210 211 /** 212 * Sets the {@code Cache-Control} header value. 213 * 214 * @param cacheControl The header value, {@code null} if not specified. 215 */ 216 public void setCacheControl(final String cacheControl) { 217 218 this.cacheControl = cacheControl; 219 } 220 221 222 /** 223 * Gets the {@code Pragma} header value. 224 * 225 * @return The header value, {@code null} if not specified. 226 */ 227 public String getPragma() { 228 229 return pragma; 230 } 231 232 233 /** 234 * Sets the {@code Pragma} header value. 235 * 236 * @param pragma The header value, {@code null} if not specified. 237 */ 238 public void setPragma(final String pragma) { 239 240 this.pragma = pragma; 241 } 242 243 244 /** 245 * Gets the {@code Www-Authenticate} header value. 246 * 247 * @return The header value, {@code null} if not specified. 248 */ 249 public String getWWWAuthenticate() { 250 251 return wwwAuthenticate; 252 } 253 254 255 /** 256 * Sets the {@code Www-Authenticate} header value. 257 * 258 * @param wwwAuthenticate The header value, {@code null} if not 259 * specified. 260 */ 261 public void setWWWAuthenticate(final String wwwAuthenticate) { 262 263 this.wwwAuthenticate = wwwAuthenticate; 264 } 265 266 267 /** 268 * Ensures this HTTP response has a specified content body. 269 * 270 * @throws ParseException If the content body is missing or empty. 271 */ 272 private void ensureContent() 273 throws ParseException { 274 275 if (content == null || content.isEmpty()) 276 throw new ParseException("Missing or empty HTTP response body"); 277 } 278 279 280 /** 281 * Gets the raw response content. 282 * 283 * @return The raw response content, {@code null} if none. 284 */ 285 public String getContent() { 286 287 return content; 288 } 289 290 291 /** 292 * Gets the response content as a JSON object. 293 * 294 * @return The response content as a JSON object. 295 * 296 * @throws ParseException If the Content-Type header isn't 297 * {@code application/json}, the response content 298 * is {@code null}, empty or couldn't be parsed 299 * to a valid JSON object. 300 */ 301 public JSONObject getContentAsJSONObject() 302 throws ParseException { 303 304 ensureContentType(CommonContentTypes.APPLICATION_JSON); 305 306 ensureContent(); 307 308 return JSONObjectUtils.parseJSONObject(content); 309 } 310 311 312 /** 313 * Gets the response content as a JSON Web Token (JWT). 314 * 315 * @return The response content as a JSON Web Token (JWT). 316 * 317 * @throws ParseException If the Content-Type header isn't 318 * {@code application/jwt}, the response content 319 * is {@code null}, empty or couldn't be parsed 320 * to a valid JSON Web Token (JWT). 321 */ 322 public JWT getContentAsJWT() 323 throws ParseException { 324 325 ensureContentType(CommonContentTypes.APPLICATION_JWT); 326 327 ensureContent(); 328 329 try { 330 return JWTParser.parse(content); 331 332 } catch (java.text.ParseException e) { 333 334 throw new ParseException(e.getMessage(), e); 335 } 336 } 337 338 339 /** 340 * Sets the raw response content. 341 * 342 * @param content The raw response content, {@code null} if none. 343 */ 344 public void setContent(final String content) { 345 346 this.content = content; 347 } 348 349 350 /** 351 * Applies the status code, headers and content of this HTTP response 352 * object to the specified HTTP servlet response. 353 * 354 * @param sr The HTTP servlet response to apply. Must not be 355 * {@code null}. 356 * 357 * @throws IOException If the response content couldn't be written. 358 */ 359 public void applyTo(final HttpServletResponse sr) 360 throws IOException { 361 362 // Set the status code 363 sr.setStatus(statusCode); 364 365 366 // Set the headers, but only if explicitly specified 367 if (location != null) 368 sr.setHeader("Location", location.toString()); 369 370 if (getContentType() != null) 371 sr.setContentType(getContentType().toString()); 372 373 if (cacheControl != null) 374 sr.setHeader("Cache-Control", cacheControl); 375 376 if (pragma != null) 377 sr.setHeader("Pragma", pragma); 378 379 380 if (wwwAuthenticate != null) 381 sr.setHeader("Www-Authenticate", wwwAuthenticate); 382 383 384 // Write out the content 385 386 if (content != null) { 387 388 PrintWriter writer = sr.getWriter(); 389 390 writer.println(content); 391 392 writer.close(); 393 } 394 } 395 }