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;
021
022import ca.uhn.fhir.rest.api.Constants;
023import jakarta.annotation.Nullable;
024import jakarta.servlet.ServletRequest;
025import jakarta.servlet.http.HttpServletRequest;
026import org.apache.commons.lang3.RandomStringUtils;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import static org.apache.commons.lang3.StringUtils.isBlank;
031import static org.apache.commons.lang3.StringUtils.isNotBlank;
032
033public class ServletRequestTracing {
034        private static final Logger ourLog = LoggerFactory.getLogger(ServletRequestTracing.class);
035        public static final String ATTRIBUTE_REQUEST_ID =
036                        ServletRequestTracing.class.getName() + '.' + Constants.HEADER_REQUEST_ID;
037
038        ServletRequestTracing() {}
039
040        /**
041         * Assign a tracing id to this request, using
042         * the X-Request-ID if present and compatible.
043         *
044         * If none present, generate a 64 random alpha-numeric string that is not
045         * cryptographically secure.
046         *
047         * @param theServletRequest the request to trace
048         * @return the tracing id
049         */
050        public static String getOrGenerateRequestId(ServletRequest theServletRequest) {
051                String requestId = maybeGetRequestId(theServletRequest);
052                if (isBlank(requestId)) {
053                        requestId = RandomStringUtils.randomAlphanumeric(Constants.REQUEST_ID_LENGTH);
054                }
055
056                ourLog.debug("Assigned tracing id {}", requestId);
057
058                theServletRequest.setAttribute(ATTRIBUTE_REQUEST_ID, requestId);
059
060                return requestId;
061        }
062
063        @Nullable
064        public static String maybeGetRequestId(ServletRequest theServletRequest) {
065                // have we already seen this request?
066                String requestId = (String) theServletRequest.getAttribute(ATTRIBUTE_REQUEST_ID);
067
068                if (requestId == null && theServletRequest instanceof HttpServletRequest) {
069                        // Also applies to non-FHIR (e.g. admin-json) requests).
070                        HttpServletRequest request = (HttpServletRequest) theServletRequest;
071                        requestId = request.getHeader(Constants.HEADER_REQUEST_ID);
072                        if (isNotBlank(requestId)) {
073                                for (char nextChar : requestId.toCharArray()) {
074                                        if (!Character.isLetterOrDigit(nextChar)) {
075                                                if (nextChar != '.' && nextChar != '-' && nextChar != '_' && nextChar != ' ') {
076                                                        requestId = null;
077                                                        break;
078                                                }
079                                        }
080                                }
081                        }
082                }
083                return requestId;
084        }
085}