在Android上解析查询字符串

Java EE有ServletRequest.getParameterValues()

在非ee平台上,URL.getQuery()只是返回一个字符串。

在Java EE上,当时正确解析URL中的查询字符串的正常方法是什么?


在回答中,尝试创建自己的解析器是很受欢迎的。这是一个非常有趣和令人兴奋的微编码项目,但是我不能说这是个好主意

下面的代码段通常是有缺陷或损坏的。对读者来说,打破它们是一项有趣的练习。还有攻击使用它们的网站的黑客

解析查询字符串是一个明确定义的问题,但阅读规范并理解其中的细微差别并非易事。最好是让一些平台库编码器为您做艰苦的工作,并进行修复!

172907 次浏览

对于servlet或JSP页面,您可以使用request.getParameter("paramname")获取查询字符串的键/值对。

String name = request.getParameter("name");

还有其他方法,但我在创建的所有servlet和jsp页面中都是这样做的。

你说“Java”,但“不是Java EE”。您的意思是您正在使用JSP和/或servlet,而不是完整的Java EE堆栈?如果是这种情况,那么您应该仍然可以使用request.getParameter()。

如果你的意思是你正在编写Java,但你没有编写jsp或servlet,或者你只是使用Java作为参考点,但你在一些没有内置参数解析的其他平台上……哇,这听起来像是一个不太可能的问题,但如果是这样的话,原则是:

xparm=0
word=""
loop
get next char
if no char
exit loop
if char=='='
param_name[xparm]=word
word=""
else if char=='&'
param_value[xparm]=word
word=""
xparm=xparm+1
else if char=='%'
read next two chars
word=word+interpret the chars as hex digits to make a byte
else
word=word+char

(我可以编写Java代码,但这将是毫无意义的,因为如果您有Java可用,您可以只使用request.getParameters。)

解析查询字符串比看起来要复杂一些,这取决于您希望有多宽容。

首先,查询字符串是ascii字节。每次读入一个字节,然后将它们转换成字符。如果角色是?或者,然后它表示参数名的开始。如果字符为=,则它表示一个参数值的开始。如果字符为%,则表示已编码字节的开始。这就是棘手的地方。

当您读入% char时,您必须读入接下来的两个字节并将它们解释为十六进制数字。这意味着接下来的两个字节是0-9,a-f或a-f。把这两个十六进制数字粘在一起得到字节值。但是记住,字节不是字符。你必须知道用什么编码来编码字符。字符é在UTF-8中的编码与在ISO-8859-1中的编码不同。一般来说,不可能知道给定字符集使用了什么编码。我总是使用UTF-8,因为我的网站被配置为总是使用UTF-8提供所有服务,但在实践中你不能确定。一些用户代理会告诉你请求中的字符编码;如果你有一个完整的HTTP请求,你可以试着读取它。如果你只有一个单独的url,祝你好运。

不管怎样,假设您正在使用UTF-8或其他一些多字节字符编码,现在您已经解码了一个已编码的字节,您必须将其放在一边,直到捕获下一个字节。您需要所有已编码的字节放在一起,因为您不能一次正确地对一个字节进行url解码。把所有在一起的字节放在一边,然后立刻解码,重新构建你的角色。

另外,如果你想要宽容一些,并解释用户代理破坏url,它会变得更有趣。例如,一些webmail客户端会对内容进行双重编码。或者重复使用?&=字符(例如:http://yoursite.com/blah??p1==v1&&p2==v2)。如果您想尝试优雅地处理这个问题,就需要向解析器添加更多的逻辑。

我认为JRE中没有。您可以在其他包(如Apache HttpClient)中找到类似的函数。如果不使用任何其他包,则只需编写自己的包。这并不难。这是我用的,

public class QueryString {


private Map<String, List<String>> parameters;


public QueryString(String qs) {
parameters = new TreeMap<String, List<String>>();


// Parse query string
String pairs[] = qs.split("&");
for (String pair : pairs) {
String name;
String value;
int pos = pair.indexOf('=');
// for "n=", the value is "", for "n", the value is null
if (pos == -1) {
name = pair;
value = null;
} else {
try {
name = URLDecoder.decode(pair.substring(0, pos), "UTF-8");
value = URLDecoder.decode(pair.substring(pos+1, pair.length()), "UTF-8");
} catch (UnsupportedEncodingException e) {
// Not really possible, throw unchecked
throw new IllegalStateException("No UTF-8");
}
}
List<String> list = parameters.get(name);
if (list == null) {
list = new ArrayList<String>();
parameters.put(name, list);
}
list.add(value);
}
}


public String getParameter(String name) {
List<String> values = parameters.get(name);
if (values == null)
return null;


if (values.size() == 0)
return "";


return values.get(0);
}


public String[] getParameterValues(String name) {
List<String> values = parameters.get(name);
if (values == null)
return null;


return (String[])values.toArray(new String[values.size()]);
}


public Enumeration<String> getParameterNames() {
return Collections.enumeration(parameters.keySet());
}


public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = new TreeMap<String, String[]>();
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
List<String> list = entry.getValue();
String[] values;
if (list == null)
values = null;
else
values = (String[]) list.toArray(new String[list.size()]);
map.put(entry.getKey(), values);
}
return map;
}
}
if (queryString != null)
{
final String[] arrParameters = queryString.split("&");
for (final String tempParameterString : arrParameters)
{
final String[] arrTempParameter = tempParameterString.split("=");
if (arrTempParameter.length >= 2)
{
final String parameterKey = arrTempParameter[0];
final String parameterValue = arrTempParameter[1];
//do something with the parameters
}
}
}

在Android上,你可以使用Uri。解析静态方法的android.net.Uri类来做繁重的工作。如果你在做任何与uri和intent相关的事情,无论如何你都会想要使用它。

public static Map<String, List<String>> getUrlParameters(String url)
throws UnsupportedEncodingException {
Map<String, List<String>> params = new HashMap<String, List<String>>();
String[] urlParts = url.split("\\?");
if (urlParts.length > 1) {
String query = urlParts[1];
for (String param : query.split("&")) {
String pair[] = param.split("=", 2);
String key = URLDecoder.decode(pair[0], "UTF-8");
String value = "";
if (pair.length > 1) {
value = URLDecoder.decode(pair[1], "UTF-8");
}
List<String> values = params.get(key);
if (values == null) {
values = new ArrayList<String>();
params.put(key, values);
}
values.add(value);
}
}
return params;
}

在Android上:

import android.net.Uri;


[...]


Uri uri=Uri.parse(url_string);
uri.getQueryParameter("para1");
public static Map <String, String> parseQueryString (final URL url)
throws UnsupportedEncodingException
{
final Map <String, String> qps = new TreeMap <String, String> ();
final StringTokenizer pairs = new StringTokenizer (url.getQuery (), "&");
while (pairs.hasMoreTokens ())
{
final String pair = pairs.nextToken ();
final StringTokenizer parts = new StringTokenizer (pair, "=");
final String name = URLDecoder.decode (parts.nextToken (), "ISO-8859-1");
final String value = URLDecoder.decode (parts.nextToken (), "ISO-8859-1");
qps.put (name, value);
}
return qps;
}

如果你的类路径上有jetty(服务器或客户端)类库,你可以使用jetty util类(参见javadoc),例如:

import org.eclipse.jetty.util.*;
URL url = new URL("www.example.com/index.php?foo=bar&bla=blub");
MultiMap<String> params = new MultiMap<String>();
UrlEncoded.decodeTo(url.getQuery(), params, "UTF-8");


assert params.getString("foo").equals("bar");
assert params.getString("bla").equals("blub");

仅供参考,这是我最终得到的结果(基于URLEncodedUtils,并返回一个Map)。

特点:

  • 它接受url的查询字符串部分(你可以使用request.getQueryString())
  • 空的查询字符串将产生空的Map
  • 没有值的参数(?test)将被映射到空的List<String>

代码:

public static Map<String, List<String>> getParameterMapOfLists(String queryString) {
Map<String, List<String>> mapOfLists = new HashMap<String, List<String>>();
if (queryString == null || queryString.length() == 0) {
return mapOfLists;
}
List<NameValuePair> list = URLEncodedUtils.parse(URI.create("http://localhost/?" + queryString), "UTF-8");
for (NameValuePair pair : list) {
List<String> values = mapOfLists.get(pair.getName());
if (values == null) {
values = new ArrayList<String>();
mapOfLists.put(pair.getName(), values);
}
if (pair.getValue() != null) {
values.add(pair.getValue());
}
}


return mapOfLists;
}

兼容性帮助器(值存储在String数组中,就像在ServletRequest.getParameterMap ()中一样):

public static Map<String, String[]> getParameterMap(String queryString) {
Map<String, List<String>> mapOfLists = getParameterMapOfLists(queryString);


Map<String, String[]> mapOfArrays = new HashMap<String, String[]>();
for (String key : mapOfLists.keySet()) {
mapOfArrays.put(key, mapOfLists.get(key).toArray(new String[] {}));
}


return mapOfArrays;
}

这对我有用.. 我不知道为什么每个人都想要一个地图,列表> 我所需要的只是一个简单的名称值Map.

为了简单起见,我使用URI.getQuery()中的构建;

public static Map<String, String> getUrlParameters(URI uri)
throws UnsupportedEncodingException {
Map<String, String> params = new HashMap<String, String>();
for (String param : uri.getQuery().split("&")) {
String pair[] = param.split("=");
String key = URLDecoder.decode(pair[0], "UTF-8");
String value = "";
if (pair.length > 1) {
value = URLDecoder.decode(pair[1], "UTF-8");
}
params.put(new String(key), new String(value));
}
return params;
}

Guava的Multimap更适合这一点。以下是一个简短的版本:

Multimap<String, String> getUrlParameters(String url) {
try {
Multimap<String, String> ret = ArrayListMultimap.create();
for (NameValuePair param : URLEncodedUtils.parse(new URI(url), "UTF-8")) {
ret.put(param.getName(), param.getValue());
}
return ret;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

使用Apache HttpComponents并将其与一些集合代码连接起来,以按值访问参数:http://www.joelgerard.com/2012/09/14/parsing-query-strings-in-java-and-accessing-values-by-key/

在Android上,我尝试使用@diyism回答,但我遇到了@rpetrich提出的空格字符问题,例如: 我填写了一个表单,其中username = "us+us"password = "pw pw"导致URL字符串看起来像:

http://somewhere?username=us%2Bus&password=pw+pw

然而,@diyism代码返回"us+us""pw+pw",即它不检测空格字符。如果URL用%20重写,则空格字符将被识别:

http://somewhere?username=us%2Bus&password=pw%20pw

这导致以下修复:

Uri uri = Uri.parse(url_string.replace("+", "%20"));
uri.getQueryParameter("para1");

使用番石榴:

Multimap<String,String> parseQueryString(String queryString, String encoding) {
LinkedListMultimap<String, String> result = LinkedListMultimap.create();


for(String entry : Splitter.on("&").omitEmptyStrings().split(queryString)) {
String pair [] = entry.split("=", 2);
try {
result.put(URLDecoder.decode(pair[0], encoding), pair.length == 2 ? URLDecoder.decode(pair[1], encoding) : null);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}


return result;
}

如果你使用的是Spring 3.1或更高版本(哎呀,希望支持更早),你可以使用UriComponentsUriComponentsBuilder:

UriComponents components = UriComponentsBuilder.fromUri(uri).build();
List<String> myParam = components.getQueryParams().get("myParam");

components.getQueryParams()返回一个MultiValueMap<String, String>

这里有更多的文档

该方法获取uri并返回票面名称和票面值的映射

  public static Map<String, String> getQueryMap(String uri) {


String queryParms[] = uri.split("\\?");


Map<String, String> map = new HashMap<>();//


if (queryParms == null || queryParms.length == 0) return map;


String[] params = queryParms[1].split("&");
for (String param : params) {
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
}
return map;
}
自从Android M以来,事情变得更加复杂。android.net.URI.getQueryParameter()的答案有一个错误,在JellyBean之前打破空格。 Apache URLEncodedUtils.parse()工作,但是在L中弃用在M中移除.

所以现在最好的答案是UrlQuerySanitizer。它从API级别1开始就存在,现在仍然存在。它还使您考虑一些棘手的问题,如如何处理特殊字符或重复值。

最简单的代码是

UrlQuerySanitizer.ValueSanitizer sanitizer = UrlQuerySanitizer.getAllButNullLegal();
// remember to decide if you want the first or last parameter with the same name
// If you want the first call setPreferFirstRepeatedParameter(true);
sanitizer.parseUrl(url);
String value = sanitizer.getValue("paramName");

如果你对默认的解析行为满意,你可以这样做:

new UrlQuerySanitizer(url).getValue("paramName")

但是您应该确保了解默认的解析行为是什么,因为它可能不是您想要的。

我有方法来实现这一点:

1):

public static String getQueryString(String url, String tag) {
String[] params = url.split("&");
Map<String, String> map = new HashMap<String, String>();
for (String param : params) {
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
}


Set<String> keys = map.keySet();
for (String key : keys) {
if(key.equals(tag)){
return map.get(key);
}
System.out.println("Name=" + key);
System.out.println("Value=" + map.get(key));
}
return "";
}

2)和最简单的方法做到这一点使用Uri类:

public static String getQueryString(String url, String tag) {
try {
Uri uri=Uri.parse(url);
return uri.getQueryParameter(tag);
}catch(Exception e){
Log.e(TAG,"getQueryString() " + e.getMessage());
}
return "";
}

这是一个如何使用两种方法之一的例子:

String url = "http://www.jorgesys.com/advertisements/publicidadmobile.htm?position=x46&site=reform&awidth=800&aheight=120";
String tagValue = getQueryString(url,"awidth");

tagValue的值是800

在Android上,代码如下所示:

UrlQuerySanitizer sanitzer = new UrlQuerySanitizer(url);
String value = sanitzer.getValue("your_get_parameter");

另外,如果你不想注册每个预期的查询键使用:

sanitzer.setAllowUnregisteredParamaters(true)

在调用之前:

sanitzer.parseUrl(yourUrl)

在这里回答,因为这是一个流行的线程。这是一个干净的Kotlin解决方案,使用了推荐的UrlQuerySanitizer api。查看官方文档。我添加了一个字符串构建器来连接和显示参数。

    var myURL: String? = null
// if the url is sent from a different activity where you set it to a value
if (intent.hasExtra("my_value")) {
myURL = intent.extras.getString("my_value")
} else {
myURL = intent.dataString
}


val sanitizer = UrlQuerySanitizer(myURL)
// We don't want to manually define every expected query *key*, so we set this to true
sanitizer.allowUnregisteredParamaters = true
val parameterNamesToValues: List<UrlQuerySanitizer.ParameterValuePair> = sanitizer.parameterList
val parameterIterator: Iterator<UrlQuerySanitizer.ParameterValuePair> = parameterNamesToValues.iterator()


// Helper simply so we can display all values on screen
val stringBuilder = StringBuilder()


while (parameterIterator.hasNext()) {
val parameterValuePair: UrlQuerySanitizer.ParameterValuePair = parameterIterator.next()
val parameterName: String = parameterValuePair.mParameter
val parameterValue: String = parameterValuePair.mValue


// Append string to display all key value pairs
stringBuilder.append("Key: $parameterName\nValue: $parameterValue\n\n")
}


// Set a textView's text to display the string
val paramListString = stringBuilder.toString()
val textView: TextView = findViewById(R.id.activity_title) as TextView
textView.text = "Paramlist is \n\n$paramListString"


// to check if the url has specific keys
if (sanitizer.hasParameter("type")) {
val type = sanitizer.getValue("type")
println("sanitizer has type param $type")
}

最初回答在这里

在Android上,包< em > android.net < / em >中有Uri类。请注意,Uri是android.net的一部分,而Uri是java.net的一部分。

Uri类有很多提取查询键值对的函数。 enter image description here < / p >

下面的函数以HashMap的形式返回键值对。

在Java中:

Map<String, String> getQueryKeyValueMap(Uri uri){
HashMap<String, String> keyValueMap = new HashMap();
String key;
String value;


Set<String> keyNamesList = uri.getQueryParameterNames();
Iterator iterator = keyNamesList.iterator();


while (iterator.hasNext()){
key = (String) iterator.next();
value = uri.getQueryParameter(key);
keyValueMap.put(key, value);
}
return keyValueMap;
}

在芬兰湾的科特林:

fun getQueryKeyValueMap(uri: Uri): HashMap<String, String> {
val keyValueMap = HashMap<String, String>()
var key: String
var value: String


val keyNamesList = uri.queryParameterNames
val iterator = keyNamesList.iterator()


while (iterator.hasNext()) {
key = iterator.next() as String
value = uri.getQueryParameter(key) as String
keyValueMap.put(key, value)
}
return keyValueMap
}