HBASE-7621 REST client (RemoteHTable) doesn't support binary row keys

Signed-off-by: Andrew Purtell <apurtell@apache.org>
This commit is contained in:
Keith David Winkler 2016-08-16 21:23:41 -05:00 committed by Andrew Purtell
parent 34b668b0a9
commit 7145e46b7a
2 changed files with 64 additions and 37 deletions

View File

@ -21,6 +21,8 @@ package org.apache.hadoop.hbase.rest.client;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@ -92,9 +94,9 @@ public class RemoteHTable implements Table {
final long startTime, final long endTime, final int maxVersions) {
StringBuffer sb = new StringBuffer();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append('/');
sb.append(Bytes.toStringBinary(row));
sb.append(toURLEncodedBytes(row));
Set families = familyMap.entrySet();
if (families != null) {
Iterator i = familyMap.entrySet().iterator();
@ -104,19 +106,18 @@ public class RemoteHTable implements Table {
Collection quals = (Collection)e.getValue();
if (quals == null || quals.isEmpty()) {
// this is an unqualified family. append the family name and NO ':'
sb.append(Bytes.toStringBinary((byte[])e.getKey()));
sb.append(toURLEncodedBytes((byte[])e.getKey()));
} else {
Iterator ii = quals.iterator();
while (ii.hasNext()) {
sb.append(Bytes.toStringBinary((byte[])e.getKey()));
sb.append(toURLEncodedBytes((byte[])e.getKey()));
sb.append(':');
Object o = ii.next();
// Puts use byte[] but Deletes use KeyValue
if (o instanceof byte[]) {
sb.append(Bytes.toStringBinary((byte[])o));
sb.append(toURLEncodedBytes((byte[])o));
} else if (o instanceof KeyValue) {
sb.append(Bytes.toStringBinary(((KeyValue) o).getQualifierArray(),
((KeyValue) o).getQualifierOffset(), ((KeyValue) o).getQualifierLength()));
sb.append(toURLEncodedBytes(CellUtil.cloneQualifier((KeyValue)o)));
} else {
throw new RuntimeException("object type not handled");
}
@ -151,7 +152,7 @@ public class RemoteHTable implements Table {
protected String buildMultiRowSpec(final byte[][] rows, int maxVersions) {
StringBuilder sb = new StringBuilder();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append("/multiget/");
if (rows == null || rows.length == 0) {
return sb.toString();
@ -163,7 +164,7 @@ public class RemoteHTable implements Table {
sb.append('&');
}
sb.append("row=");
sb.append(Bytes.toStringBinary(rk));
sb.append(toURLEncodedBytes(rk));
}
sb.append("&v=");
sb.append(maxVersions);
@ -211,8 +212,6 @@ public class RemoteHTable implements Table {
/**
* Constructor
* @param client
* @param name
*/
public RemoteHTable(Client client, String name) {
this(client, HBaseConfiguration.create(), Bytes.toBytes(name));
@ -220,9 +219,6 @@ public class RemoteHTable implements Table {
/**
* Constructor
* @param client
* @param conf
* @param name
*/
public RemoteHTable(Client client, Configuration conf, String name) {
this(client, conf, Bytes.toBytes(name));
@ -230,9 +226,6 @@ public class RemoteHTable implements Table {
/**
* Constructor
* @param client
* @param conf
* @param name
*/
public RemoteHTable(Client client, Configuration conf, byte[] name) {
this.client = client;
@ -260,7 +253,7 @@ public class RemoteHTable implements Table {
public HTableDescriptor getTableDescriptor() throws IOException {
StringBuilder sb = new StringBuilder();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append('/');
sb.append("schema");
for (int i = 0; i < maxRetries; i++) {
@ -402,9 +395,9 @@ public class RemoteHTable implements Table {
CellSetModel model = buildModelFromPut(put);
StringBuilder sb = new StringBuilder();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append('/');
sb.append(Bytes.toStringBinary(put.getRow()));
sb.append(toURLEncodedBytes(put.getRow()));
for (int i = 0; i < maxRetries; i++) {
Response response = client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF,
model.createProtobufOutput());
@ -459,7 +452,7 @@ public class RemoteHTable implements Table {
// build path for multiput
StringBuilder sb = new StringBuilder();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append("/$multiput"); // can be any nonexistent row
for (int i = 0; i < maxRetries; i++) {
Response response = client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF,
@ -530,7 +523,7 @@ public class RemoteHTable implements Table {
}
StringBuffer sb = new StringBuffer();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append('/');
sb.append("scanner");
for (int i = 0; i < maxRetries; i++) {
@ -684,9 +677,9 @@ public class RemoteHTable implements Table {
CellSetModel model = buildModelFromPut(put);
StringBuilder sb = new StringBuilder();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append('/');
sb.append(Bytes.toStringBinary(put.getRow()));
sb.append(toURLEncodedBytes(put.getRow()));
sb.append("?check=put");
for (int i = 0; i < maxRetries; i++) {
@ -728,9 +721,9 @@ public class RemoteHTable implements Table {
CellSetModel model = buildModelFromPut(put);
StringBuilder sb = new StringBuilder();
sb.append('/');
sb.append(Bytes.toStringBinary(name));
sb.append(Bytes.toString(name));
sb.append('/');
sb.append(Bytes.toStringBinary(row));
sb.append(toURLEncodedBytes(row));
sb.append("?check=delete");
for (int i = 0; i < maxRetries; i++) {
@ -890,4 +883,19 @@ public class RemoteHTable implements Table {
public void setWriteRpcTimeout(int writeRpcTimeout) {
throw new UnsupportedOperationException();
}
/*
* Only a small subset of characters are valid in URLs.
*
* Row keys, column families, and qualifiers cannot be appended to URLs without first URL
* escaping. Table names are ok because they can only contain alphanumeric, ".","_", and "-"
* which are valid characters in URLs.
*/
private static String toURLEncodedBytes(byte[] row) {
try {
return URLEncoder.encode(new String(row, "UTF-8"), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("URLEncoder doesn't support UTF-8", e);
}
}
}

View File

@ -59,16 +59,35 @@ import org.junit.experimental.categories.Category;
@Category({RestTests.class, MediumTests.class})
public class TestRemoteTable {
private static final TableName TABLE = TableName.valueOf("TestRemoteTable");
private static final byte[] ROW_1 = Bytes.toBytes("testrow1");
private static final byte[] ROW_2 = Bytes.toBytes("testrow2");
private static final byte[] ROW_3 = Bytes.toBytes("testrow3");
private static final byte[] ROW_4 = Bytes.toBytes("testrow4");
private static final byte[] COLUMN_1 = Bytes.toBytes("a");
private static final byte[] COLUMN_2 = Bytes.toBytes("b");
private static final byte[] COLUMN_3 = Bytes.toBytes("c");
private static final byte[] QUALIFIER_1 = Bytes.toBytes("1");
private static final byte[] QUALIFIER_2 = Bytes.toBytes("2");
// Verify that invalid URL characters and arbitrary bytes are escaped when
// constructing REST URLs per HBASE-7621. RemoteHTable should support row keys
// and qualifiers containing any byte for all table operations.
private static final String INVALID_URL_CHARS_1 =
"|\"\\^{}\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000B\u000C";
// HColumnDescriptor prevents certain characters in column names. The following
// are examples of characters are allowed in column names but are not valid in
// URLs.
private static final String INVALID_URL_CHARS_2 = "|^{}\u0242";
// Besides alphanumeric these characters can also be present in table names.
private static final String VALID_TABLE_NAME_CHARS = "_-.";
private static final TableName TABLE =
TableName.valueOf("TestRemoteTable" + VALID_TABLE_NAME_CHARS);
private static final byte[] ROW_1 = Bytes.toBytes("testrow1" + INVALID_URL_CHARS_1);
private static final byte[] ROW_2 = Bytes.toBytes("testrow2" + INVALID_URL_CHARS_1);
private static final byte[] ROW_3 = Bytes.toBytes("testrow3" + INVALID_URL_CHARS_1);
private static final byte[] ROW_4 = Bytes.toBytes("testrow4"+ INVALID_URL_CHARS_1);
private static final byte[] COLUMN_1 = Bytes.toBytes("a" + INVALID_URL_CHARS_2);
private static final byte[] COLUMN_2 = Bytes.toBytes("b" + INVALID_URL_CHARS_2);
private static final byte[] COLUMN_3 = Bytes.toBytes("c" + INVALID_URL_CHARS_2);
private static final byte[] QUALIFIER_1 = Bytes.toBytes("1" + INVALID_URL_CHARS_1);
private static final byte[] QUALIFIER_2 = Bytes.toBytes("2" + INVALID_URL_CHARS_1);
private static final byte[] VALUE_1 = Bytes.toBytes("testvalue1");
private static final byte[] VALUE_2 = Bytes.toBytes("testvalue2");
@ -322,7 +341,7 @@ public class TestRemoteTable {
assertNotNull(value);
assertTrue(Bytes.equals(VALUE_2, value));
assertTrue(Bytes.equals(Bytes.toBytes("TestRemoteTable"), remoteTable.getTableName()));
assertTrue(Bytes.equals(Bytes.toBytes("TestRemoteTable" + VALID_TABLE_NAME_CHARS), remoteTable.getTableName()));
}
@Test