001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.support; 018 019import javax.servlet.http.HttpServletRequest; 020 021import org.springframework.lang.Nullable; 022import org.springframework.util.Assert; 023import org.springframework.util.StringUtils; 024import org.springframework.web.context.request.RequestAttributes; 025import org.springframework.web.context.request.RequestContextHolder; 026import org.springframework.web.context.request.ServletRequestAttributes; 027import org.springframework.web.util.UriComponentsBuilder; 028import org.springframework.web.util.UriUtils; 029import org.springframework.web.util.UrlPathHelper; 030 031/** 032 * UriComponentsBuilder with additional static factory methods to create links 033 * based on the current HttpServletRequest. 034 * 035 * <p><strong>Note:</strong> As of 5.1, methods in this class do not extract 036 * {@code "Forwarded"} and {@code "X-Forwarded-*"} headers that specify the 037 * client-originated address. Please, use 038 * {@link org.springframework.web.filter.ForwardedHeaderFilter 039 * ForwardedHeaderFilter}, or similar from the underlying server, to extract 040 * and use such headers, or to discard them. 041 * 042 * @author Rossen Stoyanchev 043 * @since 3.1 044 */ 045public class ServletUriComponentsBuilder extends UriComponentsBuilder { 046 047 @Nullable 048 private String originalPath; 049 050 051 /** 052 * Default constructor. Protected to prevent direct instantiation. 053 * @see #fromContextPath(HttpServletRequest) 054 * @see #fromServletMapping(HttpServletRequest) 055 * @see #fromRequest(HttpServletRequest) 056 * @see #fromCurrentContextPath() 057 * @see #fromCurrentServletMapping() 058 * @see #fromCurrentRequest() 059 */ 060 protected ServletUriComponentsBuilder() { 061 } 062 063 /** 064 * Create a deep copy of the given ServletUriComponentsBuilder. 065 * @param other the other builder to copy from 066 */ 067 protected ServletUriComponentsBuilder(ServletUriComponentsBuilder other) { 068 super(other); 069 this.originalPath = other.originalPath; 070 } 071 072 073 // Factory methods based on an HttpServletRequest 074 075 /** 076 * Prepare a builder from the host, port, scheme, and context path of the 077 * given HttpServletRequest. 078 */ 079 public static ServletUriComponentsBuilder fromContextPath(HttpServletRequest request) { 080 ServletUriComponentsBuilder builder = initFromRequest(request); 081 builder.replacePath(request.getContextPath()); 082 return builder; 083 } 084 085 /** 086 * Prepare a builder from the host, port, scheme, context path, and 087 * servlet mapping of the given HttpServletRequest. 088 * <p>If the servlet is mapped by name, e.g. {@code "/main/*"}, the path 089 * will end with "/main". If the servlet is mapped otherwise, e.g. 090 * {@code "/"} or {@code "*.do"}, the result will be the same as 091 * if calling {@link #fromContextPath(HttpServletRequest)}. 092 */ 093 public static ServletUriComponentsBuilder fromServletMapping(HttpServletRequest request) { 094 ServletUriComponentsBuilder builder = fromContextPath(request); 095 if (StringUtils.hasText(UrlPathHelper.defaultInstance.getPathWithinServletMapping(request))) { 096 builder.path(request.getServletPath()); 097 } 098 return builder; 099 } 100 101 /** 102 * Prepare a builder from the host, port, scheme, and path (but not the query) 103 * of the HttpServletRequest. 104 */ 105 public static ServletUriComponentsBuilder fromRequestUri(HttpServletRequest request) { 106 ServletUriComponentsBuilder builder = initFromRequest(request); 107 builder.initPath(request.getRequestURI()); 108 return builder; 109 } 110 111 /** 112 * Prepare a builder by copying the scheme, host, port, path, and 113 * query string of an HttpServletRequest. 114 */ 115 public static ServletUriComponentsBuilder fromRequest(HttpServletRequest request) { 116 ServletUriComponentsBuilder builder = initFromRequest(request); 117 builder.initPath(request.getRequestURI()); 118 builder.query(request.getQueryString()); 119 return builder; 120 } 121 122 /** 123 * Initialize a builder with a scheme, host,and port (but not path and query). 124 */ 125 private static ServletUriComponentsBuilder initFromRequest(HttpServletRequest request) { 126 String scheme = request.getScheme(); 127 String host = request.getServerName(); 128 int port = request.getServerPort(); 129 130 ServletUriComponentsBuilder builder = new ServletUriComponentsBuilder(); 131 builder.scheme(scheme); 132 builder.host(host); 133 if (("http".equals(scheme) && port != 80) || ("https".equals(scheme) && port != 443)) { 134 builder.port(port); 135 } 136 return builder; 137 } 138 139 140 // Alternative methods relying on RequestContextHolder to find the request 141 142 /** 143 * Same as {@link #fromContextPath(HttpServletRequest)} except the 144 * request is obtained through {@link RequestContextHolder}. 145 */ 146 public static ServletUriComponentsBuilder fromCurrentContextPath() { 147 return fromContextPath(getCurrentRequest()); 148 } 149 150 /** 151 * Same as {@link #fromServletMapping(HttpServletRequest)} except the 152 * request is obtained through {@link RequestContextHolder}. 153 */ 154 public static ServletUriComponentsBuilder fromCurrentServletMapping() { 155 return fromServletMapping(getCurrentRequest()); 156 } 157 158 /** 159 * Same as {@link #fromRequestUri(HttpServletRequest)} except the 160 * request is obtained through {@link RequestContextHolder}. 161 */ 162 public static ServletUriComponentsBuilder fromCurrentRequestUri() { 163 return fromRequestUri(getCurrentRequest()); 164 } 165 166 /** 167 * Same as {@link #fromRequest(HttpServletRequest)} except the 168 * request is obtained through {@link RequestContextHolder}. 169 */ 170 public static ServletUriComponentsBuilder fromCurrentRequest() { 171 return fromRequest(getCurrentRequest()); 172 } 173 174 /** 175 * Obtain current request through {@link RequestContextHolder}. 176 */ 177 protected static HttpServletRequest getCurrentRequest() { 178 RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); 179 Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); 180 return ((ServletRequestAttributes) attrs).getRequest(); 181 } 182 183 184 private void initPath(String path) { 185 this.originalPath = path; 186 replacePath(path); 187 } 188 189 /** 190 * Remove any path extension from the {@link HttpServletRequest#getRequestURI() 191 * requestURI}. This method must be invoked before any calls to {@link #path(String)} 192 * or {@link #pathSegment(String...)}. 193 * <pre> 194 * GET http://www.foo.example/rest/books/6.json 195 * 196 * ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromRequestUri(this.request); 197 * String ext = builder.removePathExtension(); 198 * String uri = builder.path("/pages/1.{ext}").buildAndExpand(ext).toUriString(); 199 * assertEquals("http://www.foo.example/rest/books/6/pages/1.json", result); 200 * </pre> 201 * @return the removed path extension for possible re-use, or {@code null} 202 * @since 4.0 203 */ 204 @Nullable 205 public String removePathExtension() { 206 String extension = null; 207 if (this.originalPath != null) { 208 extension = UriUtils.extractFileExtension(this.originalPath); 209 if (StringUtils.hasLength(extension)) { 210 int end = this.originalPath.length() - (extension.length() + 1); 211 replacePath(this.originalPath.substring(0, end)); 212 } 213 this.originalPath = null; 214 } 215 return extension; 216 } 217 218 @Override 219 public ServletUriComponentsBuilder cloneBuilder() { 220 return new ServletUriComponentsBuilder(this); 221 } 222 223}