Reputation: 10531
I am using spring-boot to host a http request service.
@RequestMapping("/extract")
@SuppressWarnings("unchecked")
@ResponseBody
public ExtractionResponse extract(@RequestParam(value = "extractionInput") String input) {
// LOGGER.info("input: " + input);
JSONObject inputObject = JSON.parseObject(input);
InputInfo inputInfo = new InputInfo();
//Object object = inputObject.get(InputInfo.INPUT_INFO);
JSONObject object = (JSONObject) inputObject.get(InputInfo.INPUT_INFO);
String inputText = object.getString(InputInfo.INPUT_TEXT);
inputInfo.setInputText(inputText);
return jnService.getExtraction(inputInfo);
}
When there is a % sign, as follows, it got an errror:
http://localhost:8090/extract?extractionInput={"inputInfo":{"inputText":"5.00%"}}
The error message is below:
2018-10-09 at 19:12:53.340 [http-nio-8090-exec-1] INFO org.apache.juli.logging.DirectJDKLog [180] [log] - Character decoding failed. Parameter [extractionInput] with value [{"inputInfo":{"inputText":"5.0022:%225.00%%22}}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding. Use debug level logging to see the original, non-corrupted values.
Note: further occurrences of Parameter errors will be logged at DEBUG level.
2018-10-09 at 19:12:53.343 [http-nio-8090-exec-1] WARN org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver [140] [resolveException] - Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'extractionInput' is not present]
How to configure the URI encoding to fix this issue in my spring-boot configurations?
EDIT: Possible Java client code to make the request:
public String process(String question) {
QueryInfo queryInfo = getQueryInfo(question);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String jsonResult = null;
try {
String jsonStr = mapper.writeValueAsString(queryInfo);
String urlStr = Parameters.getQeWebserviceUrl() + URLEncoder.encode(jsonStr, "UTF-8");
URL url = new URL(urlStr);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
jsonResult = in.readLine();
in.close();
} catch (Exception jpe) {
jpe.printStackTrace();
}
return jsonResult
}
Upvotes: 3
Views: 14245
Reputation: 2655
Without encoding from your client side - you could still achieve this if you follow any of the following strategies by encoding before the request is processed in the servlet:
With any of the above cross-cutting strategies, you could encode the request URL and pass back to the endpoint.
For example below is one implmentation using Filter. You could possibly do some caching there if you need better performance.
@Component
public class SomeFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(SomeFilter.class);
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletRequest modifiedRequest = new SomeHttpServletRequest(request);
filterChain.doFilter(modifiedRequest, servletResponse);
}
@Override
public void destroy() {
}
class SomeHttpServletRequest extends HttpServletRequestWrapper {
HttpServletRequest request;
SomeHttpServletRequest(final HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public String getQueryString() {
String queryString = request.getQueryString();
LOGGER.info("Original query string: " + queryString);
try {
// You need to escape all your non encoded special characters here
String specialChar = URLEncoder.encode("%", "UTF-8");
queryString = queryString.replaceAll("\\%\\%", specialChar + "%");
String decoded = URLDecoder.decode(queryString, "UTF-8");
LOGGER.info("Modified query string: " + decoded);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return queryString;
}
@Override
public String getParameter(final String name) {
String[] params = getParameterMap().get(name);
return params.length > 0 ? params[0] : null;
}
@Override
public Map<String, String[]> getParameterMap() {
String queryString = getQueryString();
return getParamsFromQueryString(queryString);
}
@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(getParameterMap().keySet());
}
@Override
public String[] getParameterValues(final String name) {
return getParameterMap().get(name);
}
private Map<String, String[]> getParamsFromQueryString(final String queryString) {
String decoded = "";
try {
decoded = URLDecoder.decode(queryString, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] params = decoded.split("&");
Map<String, List<String>> collect = Stream.of(params)
.map(x -> x.split("="))
.collect(Collectors.groupingBy(
x -> x[0],
Collectors.mapping(
x -> x.length > 1 ? x[1] : null,
Collectors.toList())));
Map<String, String[]> result = collect.entrySet().stream()
.collect(Collectors.toMap(
x -> x.getKey(),
x -> x.getValue()
.stream()
.toArray(String[]::new)));
return result;
}
}
}
Upvotes: 4
Reputation: 3718
This is not a best practice for a REST API. Try to normalize your URLs in object oriented way to capture path variables.
if your object likes:
param1:{
param2:{
param3: ""
}
}
use url pattern to capture attribute as:
class/param1/param2/{param3}
otherwise you will get more problems when altering front-end technologies while keeping back-end REST API same.
Upvotes: 0
Reputation: 30088
You probably need to URLEncode the query parameter, e.g.
http://localhost:8090/extract?extractionInput=%7B%22inputInfo%22%3A%7B%22inputText%22%3A%225.00%25%22%7D%7D
The generally easier way to pass a parameter like this is to use an HTTP POST instead of a GET, and pass your JSON object in the body.
Upvotes: 0