Comment stocker une Réponse Http qui peuvent contenir des données binaires?
Comme je l'ai décrit dans une précédente question, j'ai une mission à écrire un serveur proxy. Il a partiellement fonctionne maintenant, mais j'ai encore un problème avec la manipulation du format de l'information. - Je stocker les HttpResponse dans une Chaîne de caractères, et il semble que je ne peux pas le faire avec le format de contenu. Cependant, les en-têtes sont texte dont j'ai besoin pour analyser, et ils viennent tous de la même InputStream
. Ma question est, que dois-je faire pour gérer correctement les réponses binaires, tout en continuant de l'analyse les en-têtes comme des chaînes de caractères?
>> voir l'édition ci-dessous avant de regarder le code.
Voici la Response
implémentation de la classe:
public class Response {
private String fullResponse = "";
private BufferedReader reader;
private boolean busy = true;
private int responseCode;
private CacheControl cacheControl;
public Response(String input) {
this(new ByteArrayInputStream(input.getBytes()));
}
public Response(InputStream input) {
reader = new BufferedReader(new InputStreamReader(input));
try {
while (!reader.ready());//wait for initialization.
String line;
while ((line = reader.readLine()) != null) {
fullResponse += "\r\n" + line;
if (HttpPatterns.RESPONSE_CODE.matches(line)) {
responseCode = (Integer) HttpPatterns.RESPONSE_CODE.process(line);
} else if (HttpPatterns.CACHE_CONTROL.matches(line)) {
cacheControl = (CacheControl) HttpPatterns.CACHE_CONTROL.process(line);
}
}
reader.close();
fullResponse = "\r\n" + fullResponse.trim() + "\r\n\r\n";
} catch (IOException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
busy = false;
}
public CacheControl getCacheControl() {
return cacheControl;
}
public String getFullResponse() {
return fullResponse;
}
public boolean isBusy() {
return busy;
}
public int getResponseCode() {
return responseCode;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((fullResponse == null) ? 0 : fullResponse.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Response))
return false;
Response other = (Response) obj;
if (fullResponse == null) {
if (other.fullResponse != null)
return false;
} else if (!fullResponse.equals(other.fullResponse))
return false;
return true;
}
@Override
public String toString() {
return "Response\n==============================\n" + fullResponse;
}
}
Et voici HttpPatterns
:
public enum HttpPatterns {
RESPONSE_CODE("^HTTP/1\\.1 (\\d+) .*$"),
CACHE_CONTROL("^Cache-Control: (\\w+)$"),
HOST("^Host: (\\w+)$"),
REQUEST_HEADER("(GET|POST) ([^\\s]+) ([^\\s]+)$"),
ACCEPT_ENCODING("^Accept-Encoding: .*$");
private final Pattern pattern;
HttpPatterns(String regex) {
pattern = Pattern.compile(regex);
}
public boolean matches(String expression) {
return pattern.matcher(expression).matches();
}
public Object process(String expression) {
Matcher matcher = pattern.matcher(expression);
if (!matcher.matches()) {
throw new RuntimeException("Called `process`, but the expression doesn't match. Call `matches` first.");
}
if (this == RESPONSE_CODE) {
return Integer.parseInt(matcher.group(1));
} else if (this == CACHE_CONTROL) {
return CacheControl.parseString(matcher.group(1));
} else if (this == HOST) {
return matcher.group(1);
} else if (this == REQUEST_HEADER) {
return new RequestHeader(RequestType.parseString(matcher.group(1)), matcher.group(2), matcher.group(3));
} else { //never happens
return null;
}
}
}
MODIFIER
J'ai essayé de mise en œuvre selon les suggestions, mais ça ne marche pas et je suis de plus désespérée. Lorsque j'essaie d'afficher une image j'obtiens le message suivant à partir du navigateur:
L'image “http://www.google.com/images/logos/ps_logo2.png” ne peut être affichée car elle contient des erreurs.
Voici le log:
Request
==============================
GET http://www.google.com/images/logos/ps_logo2.png HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:2.0) Gecko/20100101 Firefox/4.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Cookie: PREF=ID=31f95dd7f42dfc7d:TM=1303507626:LM=1303507626:S=D4kIZ6rGFrlOUWlm
Not Reading from the Cache!!!!
I am going to try to connect to: www.google.com at port 80
Connected.
Writing to the server's buffer...
flushed.
Getting a response...
Got a binary response!
contentLength = 26209; headers.length() = 312; responseLength = 12136; fullResponse length = 12136
Got a response!
Writing to the Cache!!!!
I am going to write the following response:
HTTP/1.1 200 OK
Content-Type: image/png
Last-Modified: Thu, 05 Aug 2010 22:54:44 GMT
Date: Wed, 04 May 2011 15:05:30 GMT
Expires: Wed, 04 May 2011 15:05:30 GMT
Cache-Control: private, max-age=31536000
X-Content-Type-Options: nosniff
Server: sffe
Content-Length: 26209
X-XSS-Protection: 1; mode=block
Response body is binary and was truncated.
Finished with request!
Voici le nouveau Response
classe:
public class Response {
private String headers = "";
private BufferedReader reader;
private boolean busy = true;
private int responseCode;
private CacheControl cacheControl;
private InputStream fullResponse;
private ContentEncoding encoding = ContentEncoding.TEXT;
private ContentType contentType = ContentType.TEXT;
private int contentLength;
public Response(String input) {
this(new ByteArrayInputStream(input.getBytes()));
}
public Response(InputStream input) {
ByteArrayOutputStream tempStream = new ByteArrayOutputStream();
InputStreamReader inputReader = new InputStreamReader(input);
try {
while (!inputReader.ready());
int responseLength = 0;
while (inputReader.ready()) {
tempStream.write(inputReader.read());
responseLength++;
}
/*
* Read the headers
*/
reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(tempStream.toByteArray())));
while (!reader.ready());//wait for initialization.
String line;
while ((line = reader.readLine()) != null) {
headers += "\r\n" + line;
if (HttpPatterns.RESPONSE_CODE.matches(line)) {
responseCode = (Integer) HttpPatterns.RESPONSE_CODE.process(line);
} else if (HttpPatterns.CACHE_CONTROL.matches(line)) {
cacheControl = (CacheControl) HttpPatterns.CACHE_CONTROL.process(line);
} else if (HttpPatterns.CONTENT_ENCODING.matches(line)) {
encoding = (ContentEncoding) HttpPatterns.CONTENT_ENCODING.process(line);
} else if (HttpPatterns.CONTENT_TYPE.matches(line)) {
contentType = (ContentType) HttpPatterns.CONTENT_TYPE.process(line);
} else if (HttpPatterns.CONTENT_LENGTH.matches(line)) {
contentLength = (Integer) HttpPatterns.CONTENT_LENGTH.process(line);
} else if (line.isEmpty()) {
break;
}
}
InputStreamReader streamReader = new InputStreamReader(new ByteArrayInputStream(tempStream.toByteArray()));
while (!reader.ready());//wait for initialization.
//Now let's get the rest
ByteArrayOutputStream out = new ByteArrayOutputStream();
int counter = 0;
while (streamReader.ready() && counter < (responseLength - contentLength)) {
out.write((char) streamReader.read());
counter++;
}
if (encoding == ContentEncoding.BINARY || contentType == ContentType.BINARY) {
System.out.println("Got a binary response!");
while (streamReader.ready()) {
out.write(streamReader.read());
}
} else {
System.out.println("Got a text response!");
while (streamReader.ready()) {
out.write((char) streamReader.read());
}
}
fullResponse = new ByteArrayInputStream(out.toByteArray());
System.out.println("\n\ncontentLength = " + contentLength +
"; headers.length() = " + headers.length() +
"; responseLength = " + responseLength +
"; fullResponse length = " + out.toByteArray().length + "\n\n");
} catch (IOException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
busy = false;
}
}
et voici le ProxyServer
classe:
class ProxyServer {
public void start() {
while (true) {
Socket serverSocket;
Socket clientSocket;
OutputStreamWriter toClient;
BufferedWriter toServer;
try {
//The client is meant to put data on the port, read the socket.
clientSocket = listeningSocket.accept();
Request request = new Request(clientSocket.getInputStream());
//System.out.println("Accepted a request!\n" + request);
while(request.busy);
//Make a connection to a real proxy.
//Host & Port - should be read from the request
URL url = null;
try {
url = new URL(request.getRequestURL());
} catch (MalformedURLException e){
url = new URL("http:\\"+request.getRequestHost()+request.getRequestURL());
}
System.out.println(request);
//remove entry from cache if needed
if (!request.getCacheControl().equals(CacheControl.CACHE) && cache.containsRequest(request)) {
cache.remove(request);
}
Response response = null;
if (request.getRequestType() == RequestType.GET && request.getCacheControl().equals(CacheControl.CACHE) && cache.containsRequest(request)) {
System.out.println("Reading from the Cache!!!!");
response = cache.get(request);
} else {
System.out.println("Not Reading from the Cache!!!!");
//Get the response from the destination
int remotePort = (url.getPort() == -1) ? 80 : url.getPort();
System.out.println("I am going to try to connect to: " + url.getHost() + " at port " + remotePort);
serverSocket = new Socket(url.getHost(), remotePort);
System.out.println("Connected.");
serverSocket.setSoTimeout(50000);
//write to the server - keep it open.
System.out.println("Writing to the server's buffer...");
toServer = new BufferedWriter(new OutputStreamWriter(serverSocket.getOutputStream()));
toServer.write(request.getFullRequest());
toServer.flush();
System.out.println("flushed.");
System.out.println("Getting a response...");
response = new Response(serverSocket.getInputStream());
//System.out.println("Got a response!\n" + response);
System.out.println("Got a response!\n");
//wait for the response
while(response.isBusy());
}
if (request.getRequestType() == RequestType.GET && request.getCacheControl().equals(CacheControl.CACHE) && response.getResponseCode() == 200) {
System.out.println("Writing to the Cache!!!!");
cache.put(request, response);
}
else System.out.println("Not Writing to the Cache!!!!");
response = filter.filter(response);
//Return the response to the client
toClient = new OutputStreamWriter(clientSocket.getOutputStream());
System.out.println("I am going to write the following response:\n" + response);
BufferedReader responseReader = new BufferedReader(new InputStreamReader(response.getFullResponse()));
while (responseReader.ready()) {
toClient.write(responseReader.read());
}
toClient.flush();
toClient.close();
clientSocket.close();
System.out.println("Finished with request!");
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
}
J'apprécie tous vos commentaires/connaissances/suggestion quant à la façon de résoudre ce problème, et aurait, bien sûr, préférez un peu de code.
OriginalL'auteur Amir Rachum | 2011-04-25
Vous devez vous connecter pour publier un commentaire.
De le stocker dans un tableau d'octets:
Plus détaillée du processus:
\r\n\r\n
dans la mémoire tampon. Vous pouvez écrire une fonction d'aide pour exemplestatic int arrayIndexOf(byte[] haystack, int offset, int length, byte[] needle)
Edit:
Vous ne suivez pas ces étapes, j'ai suggéré.
inputReader.ready()
est une mauvaise façon de détecter les phases de la réponse. Il n'y a aucune garantie que l'en-tête sera envoyé dans un seul rafale.J'ai essayé d'écrire un schémas dans le code (à l'exception de la arrayIndexOf) de la fonction.
La
arrayIndexOf
méthode pourrait ressembler à quelque chose comme ceci: (il y a probablement plus rapide versions)Rachum vous pouvez extraire la partie jusqu'à ce que
\r\n\r\n
à une Chaîne.veuillez voir mon edit
J'ai édité la réponse.
merci, vous avez sauvé la journée! Sera la récompense après la limite de temps.
OriginalL'auteur vbence
En gros, vous devez analyser les en-têtes de réponse en tant que texte, et le reste sous forme binaire. C'est un peu difficile à faire, car vous ne pouvez pas il suffit de créer un
InputStreamReader
autour de ce cours d'eau qui permettra de lire plus de données que vous le souhaitez. Vous aurez très probablement besoin de lire des données dans un tableau d'octets, et ensuite appelerEncoding.GetString
manuellement. Alternativement, si vous avez lu les données dans un tableau d'octets déjà, vous pouvez toujours créer unByteArrayInputStream
autour de cela, puis unInputStreamReader
sur le dessus... mais vous aurez besoin de travailler sur la façon de loin les en-têtes avant de vous obtenir le corps de la réponse, vous devez garder en tant que données binaires.Bah, va modifier 🙂
vous avez dit "[...] le corps de la réponse, vous devez garder en tant que données binaires", mais qu'est-ce que cela signifie exactement?
byte[]
?char[]
? à l'aide deGZIPInputStream
? Je ne sais pas dans quelle forme pour le garder.byte[]
est pour des données binaires,char[]
est des données de texte. L'approche la plus simple est sans doute de lire un morceau à la fois, d'écrire dans unByteArrayOutputStream
, et puis vous pouvez obtenir le tableau d'octets par la suite. Il serait plus simple si vous pouviez utiliser un existant HTTP bibliothèque de cours...veuillez voir mon edit
OriginalL'auteur Jon Skeet
Jersey — un haut niveau de framework web — peut sauver votre journée. Vous n'avez pas à gérer gzip contenu, en-tête, etc, plus soi-même.
Le code suivant renvoie l'image utilisée pour votre exemple et l'enregistrer sur le disque. Ensuite, il vérifie l'image enregistrée est égale à la télécharger sur:
Vous aurez besoin de deux dépendances maven pour l'exécuter:
OriginalL'auteur yves amsellem
Après la lecture des en-têtes avec
BufferedReader
vous aurez besoin de détecter si leContent-Encoding
- tête est réglé àgzip
. Si c'est pour lire le corps que vous aurez à passer à l'aide de laInputStream
et l'envelopper avec unGZIPInputStream
pour décoder le corps. La partie la plus délicate, cependant, est le fait que leBufferedReader
aura tamponnée passé les en-têtes dans le corps et le sous-jacentInputStream
seront à l'avance de l'endroit où vous en avez besoin.Ce que vous pourriez faire est de conclure la première
InputStream
avec unBufferedInputStream
et appelmark()
avant de commencer le traitement des en-têtes. Lorsque vous avez terminé le traitement des en-têtes d'appelreset()
. Ensuite, lisez ce cours d'eau jusqu'à ce que vous atteignez la ligne vide entre les en-têtes et le corps. Maintenant envelopper avec laGZIPInputStream
pour traiter le corps.OriginalL'auteur WhiteFang34
J'ai eu le même problème. J'ai commenté la ligne qui ajoute l'en-tête accept gzip:
...et cela a fonctionné!
OriginalL'auteur Mauricio Corrêa