
001/* 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.server.servlet; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 025import ca.uhn.fhir.rest.api.Constants; 026import ca.uhn.fhir.rest.api.PreferHeader; 027import ca.uhn.fhir.rest.api.server.RequestDetails; 028import ca.uhn.fhir.rest.server.RestfulServer; 029import ca.uhn.fhir.rest.server.RestfulServerUtils; 030import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 031import org.apache.commons.io.IOUtils; 032import org.apache.commons.lang3.Validate; 033 034import javax.annotation.Nonnull; 035import javax.servlet.http.HttpServletRequest; 036import javax.servlet.http.HttpServletResponse; 037import java.io.ByteArrayInputStream; 038import java.io.IOException; 039import java.io.InputStream; 040import java.io.Reader; 041import java.nio.charset.Charset; 042import java.util.ArrayList; 043import java.util.Collections; 044import java.util.Enumeration; 045import java.util.HashMap; 046import java.util.Iterator; 047import java.util.List; 048import java.util.Map; 049import java.util.StringTokenizer; 050import java.util.zip.GZIPInputStream; 051 052import static org.apache.commons.lang3.StringUtils.isNotBlank; 053import static org.apache.commons.lang3.StringUtils.trim; 054 055public class ServletRequestDetails extends RequestDetails { 056 057 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class); 058 059 private RestfulServer myServer; 060 private HttpServletRequest myServletRequest; 061 private HttpServletResponse myServletResponse; 062 063 /** 064 * Constructor for testing only 065 */ 066 public ServletRequestDetails() { 067 this((IInterceptorBroadcaster) null); 068 } 069 070 /** 071 * Constructor 072 */ 073 public ServletRequestDetails(IInterceptorBroadcaster theInterceptorBroadcaster) { 074 super(theInterceptorBroadcaster); 075 setResponse(new ServletRestfulResponse(this)); 076 } 077 078 /** 079 * Copy constructor 080 */ 081 public ServletRequestDetails(ServletRequestDetails theRequestDetails) { 082 super(theRequestDetails); 083 084 myServer = theRequestDetails.getServer(); 085 myServletRequest = theRequestDetails.getServletRequest(); 086 myServletResponse = theRequestDetails.getServletResponse(); 087 } 088 089 @Override 090 protected byte[] getByteStreamRequestContents() { 091 try { 092 InputStream inputStream = getInputStream(); 093 byte[] requestContents = IOUtils.toByteArray(inputStream); 094 095 if (myServer.isUncompressIncomingContents()) { 096 String contentEncoding = myServletRequest.getHeader(Constants.HEADER_CONTENT_ENCODING); 097 if ("gzip".equals(contentEncoding)) { 098 ourLog.debug("Uncompressing (GZip) incoming content"); 099 if (requestContents.length > 0) { 100 GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(requestContents)); 101 requestContents = IOUtils.toByteArray(gis); 102 } 103 } 104 } 105 return requestContents; 106 } catch (IOException e) { 107 ourLog.error("Could not load request resource", e); 108 throw new InvalidRequestException(Msg.code(308) + String.format("Could not load request resource: %s", e.getMessage())); 109 } 110 } 111 112 @Override 113 public Charset getCharset() { 114 Charset charset = null; 115 116 String charsetString = myServletRequest.getCharacterEncoding(); 117 if (isNotBlank(charsetString)) { 118 charset = Charset.forName(charsetString); 119 } 120 121 return charset; 122 } 123 124 @Override 125 public FhirContext getFhirContext() { 126 return getServer().getFhirContext(); 127 } 128 129 @Override 130 public String getHeader(String name) { 131 return getServletRequest().getHeader(name); 132 } 133 134 @Override 135 public List<String> getHeaders(String name) { 136 Enumeration<String> headers = getServletRequest().getHeaders(name); 137 return headers == null ? Collections.emptyList() : Collections.list(getServletRequest().getHeaders(name)); 138 } 139 140 @Override 141 public Object getAttribute(String theAttributeName) { 142 Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank"); 143 return getServletRequest().getAttribute(theAttributeName); 144 } 145 146 @Override 147 public void setAttribute(String theAttributeName, Object theAttributeValue) { 148 Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank"); 149 getServletRequest().setAttribute(theAttributeName, theAttributeValue); 150 } 151 152 @Override 153 public InputStream getInputStream() throws IOException { 154 return getServletRequest().getInputStream(); 155 } 156 157 @Override 158 public Reader getReader() throws IOException { 159 return getServletRequest().getReader(); 160 } 161 162 @Override 163 public RestfulServer getServer() { 164 return myServer; 165 } 166 167 @Override 168 public String getServerBaseForRequest() { 169 return getServer().getServerBaseForRequest(this); 170 } 171 172 public HttpServletRequest getServletRequest() { 173 return myServletRequest; 174 } 175 176 public HttpServletResponse getServletResponse() { 177 return myServletResponse; 178 } 179 180 public void setServer(RestfulServer theServer) { 181 this.myServer = theServer; 182 } 183 184 public ServletRequestDetails setServletRequest(@Nonnull HttpServletRequest myServletRequest) { 185 this.myServletRequest = myServletRequest; 186 187 // TODO KHS move a bunch of other initialization from RestfulServer into this method 188 if ("true".equals(myServletRequest.getHeader(Constants.HEADER_REWRITE_HISTORY))) { 189 setRewriteHistory(true); 190 } 191 setRetryFields(myServletRequest); 192 return this; 193 } 194 195 private void setRetryFields(HttpServletRequest theRequest){ 196 if (theRequest == null){ 197 return; 198 } 199 Enumeration<String> headers = theRequest.getHeaders(Constants.HEADER_RETRY_ON_VERSION_CONFLICT); 200 if (headers != null) { 201 Iterator<String> headerIterator = headers.asIterator(); 202 while(headerIterator.hasNext()){ 203 String headerValue = headerIterator.next(); 204 if (isNotBlank(headerValue)) { 205 StringTokenizer tok = new StringTokenizer(headerValue, ";"); 206 while (tok.hasMoreTokens()) { 207 String next = trim(tok.nextToken()); 208 if (next.equals(Constants.HEADER_RETRY)) { 209 setRetry(true); 210 } else if (next.startsWith(Constants.HEADER_MAX_RETRIES + "=")) { 211 String val = trim(next.substring((Constants.HEADER_MAX_RETRIES + "=").length())); 212 int maxRetries = Integer.parseInt(val); 213 setMaxRetries(maxRetries); 214 } 215 } 216 } 217 } 218 } 219 } 220 221 public void setServletResponse(HttpServletResponse myServletResponse) { 222 this.myServletResponse = myServletResponse; 223 } 224 225 public Map<String, List<String>> getHeaders() { 226 Map<String, List<String>> retVal = new HashMap<>(); 227 Enumeration<String> names = myServletRequest.getHeaderNames(); 228 while (names.hasMoreElements()) { 229 String nextName = names.nextElement(); 230 ArrayList<String> headerValues = new ArrayList<>(); 231 retVal.put(nextName, headerValues); 232 Enumeration<String> valuesEnum = myServletRequest.getHeaders(nextName); 233 while (valuesEnum.hasMoreElements()) { 234 headerValues.add(valuesEnum.nextElement()); 235 } 236 } 237 return Collections.unmodifiableMap(retVal); 238 } 239 240 /** 241 * Returns true if the `Prefer` header contains a value of `respond-async` 242 */ 243 public boolean isPreferRespondAsync() { 244 String preferHeader = getHeader(Constants.HEADER_PREFER); 245 PreferHeader prefer = RestfulServerUtils.parsePreferHeader(null, preferHeader); 246 return prefer.getRespondAsync(); 247 } 248 249}