Spring 3 RequestMapping: obtener el valor de la ruta

133

¿Hay alguna forma de obtener el valor completo de la ruta después de requestMapping @PathVariableanalizar los valores?

Es decir: /{id}/{restOfTheUrl}debe ser capaz de analizar /1/dir1/dir2/file.htmlen id=1yrestOfTheUrl=/dir1/dir2/file.html

Cualquier idea sería apreciada.

Mono de primavera
fuente

Respuestas:

198

La parte no coincidente de la URL se expone como un atributo de solicitud denominado HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE:

@RequestMapping("/{id}/**")
public void foo(@PathVariable("id") int id, HttpServletRequest request) {
    String restOfTheUrl = (String) request.getAttribute(
        HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    ...
}
axtavt
fuente
63
No, el atributo HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE contiene la ruta COMPLETA.
uthark
11
Uthark tiene razón. El valor restOfTheUrlserá la ruta completa, no solo la parte restante capturada por**
dcstraw
44
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE es opcional y puede ser NULL o "" para algunas implementaciones. request.getRequestURI () devuelve el mismo valor y no es opcional.
nidalpres
2
Esta solución ya no funciona y no es confiable.
DolphinJava
51

Acabo de encontrar ese problema correspondiente a mi problema. Usando las constantes de HandlerMapping pude escribir una pequeña utilidad para ese propósito:

/**
 * Extract path from a controller mapping. /controllerUrl/** => return matched **
 * @param request incoming request.
 * @return extracted path
 */
public static String extractPathFromPattern(final HttpServletRequest request){


    String path = (String) request.getAttribute(
            HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    String bestMatchPattern = (String ) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);

    AntPathMatcher apm = new AntPathMatcher();
    String finalPath = apm.extractPathWithinPattern(bestMatchPattern, path);

    return finalPath;

}
Fabien Kruba
fuente
19

Esto ha estado aquí bastante tiempo pero publicando esto. Puede ser útil para alguien.

@RequestMapping( "/{id}/**" )
public void foo( @PathVariable String id, HttpServletRequest request ) {
    String urlTail = new AntPathMatcher()
            .extractPathWithinPattern( "/{id}/**", request.getRequestURI() );
}
Daniel Jay Marcaida
fuente
1
El problema con este código es que no maneja el prefijo de servlet y el prefijo de mapeo.
Gavenkoa
11

Sobre la base de la excelente respuesta de Fabien Kruba , pensé que sería bueno si la **porción de la URL se pudiera dar como un parámetro para el método del controlador a través de una anotación, de manera similar @RequestParamy en @PathVariablelugar de usar siempre un método de utilidad que explícitamente requirió el HttpServletRequest. Así que aquí hay un ejemplo de cómo se podría implementar eso. Esperemos que alguien lo encuentre útil.

Cree la anotación, junto con el argumento resolutor:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WildcardParam {

    class Resolver implements HandlerMethodArgumentResolver {

        @Override
        public boolean supportsParameter(MethodParameter methodParameter) {
            return methodParameter.getParameterAnnotation(WildcardParam.class) != null;
        }

        @Override
        public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
            HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
            return request == null ? null : new AntPathMatcher().extractPathWithinPattern(
                    (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE),
                    (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
        }

    }

}

Registre el método de resolución de argumentos:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new WildcardParam.Resolver());
    }

}

Use la anotación en los métodos de su controlador de controlador para tener acceso fácil a la **parte de la URL:

@RestController
public class SomeController {

    @GetMapping("/**")
    public void someHandlerMethod(@WildcardParam String wildcardParam) {
        // use wildcardParam here...
    }

}
thejonwithnoh
fuente
9

Necesita usar incorporado pathMatcher:

@RequestMapping("/{id}/**")
public void test(HttpServletRequest request, @PathVariable long id) throws Exception {
    ResourceUrlProvider urlProvider = (ResourceUrlProvider) request
            .getAttribute(ResourceUrlProvider.class.getCanonicalName());
    String restOfUrl = urlProvider.getPathMatcher().extractPathWithinPattern(
            String.valueOf(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)),
            String.valueOf(request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE)));
Severo
fuente
2
Confirmando que esto funciona con la última versión de Spring Boot
Dave Bauman el
1
También confirma que este método funciona a partir de Spring Boot 2.2.4 RELEASE.
tom_mai78101
5

He usado el Tuckey URLRewriteFilter para manejar elementos de ruta que contienen caracteres '/', ya que no creo que Spring 3 MVC los admita todavía.

http://www.tuckey.org/

Pones este filtro en tu aplicación y proporcionas un archivo de configuración XML. En ese archivo, proporciona reglas de reescritura, que puede usar para traducir elementos de ruta que contienen caracteres '/' en parámetros de solicitud que Spring MVC puede manejar adecuadamente usando @RequestParam.

WEB-INF / web.xml:

<filter>
  <filter-name>UrlRewriteFilter</filter-name>
  <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<!-- map to /* -->

WEB-INF / urlrewrite.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite
    PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
    "http://tuckey.org/res/dtds/urlrewrite3.0.dtd">
<urlrewrite>
  <rule>
    <from>^/(.*)/(.*)$</from>
    <to last="true">/$1?restOfTheUrl=$2</to>
</urlrewrite>

Método del controlador:

@RequestMapping("/{id}")
public void handler(@PathVariable("id") int id, @RequestParam("restOfTheUrl") String pathToFile) {
  ...
}
sdouglass
fuente
2

Sí, restOfTheUrlno está devolviendo solo el valor requerido, pero podemos obtener el valor utilizando la UriTemplatecoincidencia.

He resuelto el problema, así que aquí la solución de trabajo para el problema:

@RequestMapping("/{id}/**")
public void foo(@PathVariable("id") int id, HttpServletRequest request) {
String restOfTheUrl = (String) request.getAttribute(
    HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    /*We can use UriTemplate to map the restOfTheUrl*/
    UriTemplate template = new UriTemplate("/{id}/{value}");        
    boolean isTemplateMatched = template.matches(restOfTheUrl);
    if(isTemplateMatched) {
        Map<String, String> matchTemplate = new HashMap<String, String>();
        matchTemplate = template.match(restOfTheUrl);
        String value = matchTemplate.get("value");
       /*variable `value` will contain the required detail.*/
    }
}
Anil Kumar Pandey
fuente
1

Así es como lo hice. Puede ver cómo convierto la URL solicitada en una ruta del sistema de archivos (de qué se trata esta pregunta SO). Bono: y también cómo responder con el archivo.

@RequestMapping(value = "/file/{userId}/**", method = RequestMethod.GET)
public void serveFile(@PathVariable("userId") long userId, HttpServletRequest request, HttpServletResponse response) {
    assert request != null;
    assert response != null;

    // requestURL:  http://192.168.1.3:8080/file/54/documents/tutorial.pdf
    // requestURI:  /file/54/documents/tutorial.pdf
    // servletPath: /file/54/documents/tutorial.pdf
    // logger.debug("requestURL: " + request.getRequestURL());
    // logger.debug("requestURI: " + request.getRequestURI());
    // logger.debug("servletPath: " + request.getServletPath());

    String requestURI = request.getRequestURI();
    String relativePath = requestURI.replaceFirst("^/file/", "");

    Path path = Paths.get("/user_files").resolve(relativePath);
    try {
        InputStream is = new FileInputStream(path.toFile());  
        org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
        response.flushBuffer();
    } catch (IOException ex) {
        logger.error("Error writing file to output stream. Path: '" + path + "', requestURI: '" + requestURI + "'");
        throw new RuntimeException("IOError writing file to output stream");
    }
}
neoneye
fuente
0
private final static String MAPPING = "/foo/*";

@RequestMapping(value = MAPPING, method = RequestMethod.GET)
public @ResponseBody void foo(HttpServletRequest request, HttpServletResponse response) {
    final String mapping = getMapping("foo").replace("*", ""); 
    final String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    final String restOfPath = url.replace(mapping, "");
    System.out.println(restOfPath);
}

private String getMapping(String methodName) {
    Method methods[] = this.getClass().getMethods();
    for (int i = 0; i < methods.length; i++) {
        if (methods[i].getName() == methodName) {
            String mapping[] = methods[i].getAnnotation(RequestMapping.class).value();
            if (mapping.length > 0) {
                return mapping[mapping.length - 1];
            }
        }
    }
    return null;
}
xsampedro
fuente
-4

Tengo un problema similar y resolví de esta manera:

@RequestMapping(value = "{siteCode}/**/{fileName}.{fileExtension}")
public HttpEntity<byte[]> getResource(@PathVariable String siteCode,
        @PathVariable String fileName, @PathVariable String fileExtension,
        HttpServletRequest req, HttpServletResponse response ) throws IOException {
    String fullPath = req.getPathInfo();
    // Calling http://localhost:8080/SiteXX/images/argentine/flag.jpg
    // fullPath conentent: /SiteXX/images/argentine/flag.jpg
}

Tenga en cuenta que req.getPathInfo()devolverá la ruta completa (con {siteCode}y {fileName}.{fileExtension}), por lo que deberá procesarla convenientemente.

Nico's
fuente