001/* 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2024 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 jakarta.annotation.Nonnull; 032import jakarta.servlet.http.HttpServletRequest; 033import jakarta.servlet.http.HttpServletResponse; 034import org.apache.commons.io.IOUtils; 035import org.apache.commons.lang3.Validate; 036 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( 109 Msg.code(308) + String.format("Could not load request resource: %s", e.getMessage())); 110 } 111 } 112 113 @Override 114 public Charset getCharset() { 115 Charset charset = null; 116 117 String charsetString = myServletRequest.getCharacterEncoding(); 118 if (isNotBlank(charsetString)) { 119 charset = Charset.forName(charsetString); 120 } 121 122 return charset; 123 } 124 125 @Override 126 public FhirContext getFhirContext() { 127 return getServer().getFhirContext(); 128 } 129 130 @Override 131 public String getHeader(String name) { 132 return getServletRequest().getHeader(name); 133 } 134 135 @Override 136 public List<String> getHeaders(String name) { 137 Enumeration<String> headers = getServletRequest().getHeaders(name); 138 return headers == null 139 ? Collections.emptyList() 140 : Collections.list(getServletRequest().getHeaders(name)); 141 } 142 143 @Override 144 public Object getAttribute(String theAttributeName) { 145 Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank"); 146 return getServletRequest().getAttribute(theAttributeName); 147 } 148 149 @Override 150 public void setAttribute(String theAttributeName, Object theAttributeValue) { 151 Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank"); 152 getServletRequest().setAttribute(theAttributeName, theAttributeValue); 153 } 154 155 @Override 156 public InputStream getInputStream() throws IOException { 157 return getServletRequest().getInputStream(); 158 } 159 160 @Override 161 public Reader getReader() throws IOException { 162 return getServletRequest().getReader(); 163 } 164 165 @Override 166 public RestfulServer getServer() { 167 return myServer; 168 } 169 170 @Override 171 public String getServerBaseForRequest() { 172 return getServer().getServerBaseForRequest(this); 173 } 174 175 public HttpServletRequest getServletRequest() { 176 return myServletRequest; 177 } 178 179 public HttpServletResponse getServletResponse() { 180 return myServletResponse; 181 } 182 183 public void setServer(RestfulServer theServer) { 184 this.myServer = theServer; 185 } 186 187 public ServletRequestDetails setServletRequest(@Nonnull HttpServletRequest myServletRequest) { 188 this.myServletRequest = myServletRequest; 189 190 // TODO KHS move a bunch of other initialization from RestfulServer into this method 191 if ("true".equals(myServletRequest.getHeader(Constants.HEADER_REWRITE_HISTORY))) { 192 setRewriteHistory(true); 193 } 194 setRetryFields(myServletRequest); 195 return this; 196 } 197 198 private void setRetryFields(HttpServletRequest theRequest) { 199 if (theRequest == null) { 200 return; 201 } 202 Enumeration<String> headers = theRequest.getHeaders(Constants.HEADER_RETRY_ON_VERSION_CONFLICT); 203 if (headers != null) { 204 Iterator<String> headerIterator = headers.asIterator(); 205 while (headerIterator.hasNext()) { 206 String headerValue = headerIterator.next(); 207 if (isNotBlank(headerValue)) { 208 StringTokenizer tok = new StringTokenizer(headerValue, ";"); 209 while (tok.hasMoreTokens()) { 210 String next = trim(tok.nextToken()); 211 if (next.equals(Constants.HEADER_RETRY)) { 212 setRetry(true); 213 } else if (next.startsWith(Constants.HEADER_MAX_RETRIES + "=")) { 214 String val = trim(next.substring((Constants.HEADER_MAX_RETRIES + "=").length())); 215 int maxRetries = Integer.parseInt(val); 216 setMaxRetries(maxRetries); 217 } 218 } 219 } 220 } 221 } 222 } 223 224 public void setServletResponse(HttpServletResponse myServletResponse) { 225 this.myServletResponse = myServletResponse; 226 } 227 228 public Map<String, List<String>> getHeaders() { 229 Map<String, List<String>> retVal = new HashMap<>(); 230 Enumeration<String> names = myServletRequest.getHeaderNames(); 231 while (names.hasMoreElements()) { 232 String nextName = names.nextElement(); 233 ArrayList<String> headerValues = new ArrayList<>(); 234 retVal.put(nextName, headerValues); 235 Enumeration<String> valuesEnum = myServletRequest.getHeaders(nextName); 236 while (valuesEnum.hasMoreElements()) { 237 headerValues.add(valuesEnum.nextElement()); 238 } 239 } 240 return Collections.unmodifiableMap(retVal); 241 } 242 243 /** 244 * Returns true if the `Prefer` header contains a value of `respond-async` 245 */ 246 public boolean isPreferRespondAsync() { 247 String preferHeader = getHeader(Constants.HEADER_PREFER); 248 PreferHeader prefer = RestfulServerUtils.parsePreferHeader(null, preferHeader); 249 return prefer.getRespondAsync(); 250 } 251}