Hur man använder java.net.URLConnection för att starta och hantera HTTP-begäranden
Användning av java.net.URLConnection
frågas ganska ofta här, och Oracle tutorial är för kortfattad om det.
Den handledningen visar i princip bara hur man skickar en GET-förfrågan och läser svaret. Den förklarar inte någonstans hur man använder den för att bland annat utföra en POST-förfrågan, ställa in förfrågningshuvuden, läsa svarshuvuden, hantera cookies, skicka ett HTML-formulär, ladda upp en fil osv.
Så hur kan jag använda java.net.URLConnection
för att avfyra och hantera "avancerade" HTTP-förfrågningar?
1899
3
Först en ansvarsfriskrivning i förväg: de kodutdrag som publiceras är alla grundläggande exempel. Du måste själv hantera triviala
IOException
s ochRuntimeException
s somNullPointerException
,ArrayIndexOutOfBoundsException
och liknande.Förberedelser
Vi måste först känna till åtminstone URL och charset. Parametrarna är valfria och beror på funktionskraven.
Förfrågningsparametrarna måste vara i formatet
name=value
och sammanfogas med&
. Du skulle normalt också URL-koda frågeparametrarna med det angivna charsetet med hjälp avURLEncoder#encode()
.String#format()
är bara för bekvämlighetens skull. Jag föredrar den när jag behöver String concatenation operatorn+
mer än två gånger.Förfrågan HTTP GET med (valfritt) frågeparametrar
Det är en trivial uppgift. Det är standardmetoden för begäran.
Varje frågeserie ska konkateneras till URL:en med hjälp av
?
. HuvudetAccept-Charset
kan ge servern en antydan om vilken kodning parametrarna har. Om du inte skickar någon frågeteckensträng kan du låta bli att angeAccept-Charset
-huvudet. Om du inte behöver ställa in några headers kan du till och med använda genvägsmetodenURL#openStream()
.Hur som helst, om den andra sidan är en
HttpServlet
, så kommer dessdoGet()
metod att anropas och parametrarna kommer att vara tillgängliga genomHttpServletRequest#getParameter()
. För teständamål kan du skriva ut svarskroppen till stdout enligt nedan:Förfrågan HTTP POST med frågeparametrar
Genom att ställa in
URLConnection#setDoOutput()
tilltrue
ställs förfrågningsmetoden implicit in på POST. Standard-HTTP POST som webbformulär är av typenapplication/x-www-form-urlencoded
, där frågeserien skrivs in i förfrågningskroppen.Notera: När du vill skicka ett HTML-formulär programmatiskt, glöm inte att ta med paren
name=value
för alla<input type="hidden">
-element i frågeserien och naturligtvis även parenname=value
för<input type=">
i frågeserien och naturligtvis även parenname=value
för<input type="submit">
-elementet som du vill "trycka" programmatiskt (eftersom detta vanligtvis används på serversidan för att skilja ut om en knapp har tryckts och i så fall vilken). Du kan också kasta den erhållnaURLConnection
tillHttpURLConnection
och använda dessHttpURLConnection#setRequestMethod()
istället. Men om du försöker använda anslutningen för utdata måste du fortfarande ställa inURLConnection#setDoOutput()
tilltrue
.Hur som helst, om den andra sidan är en
HttpServlet
, så kommer dessdoPost()
metod att anropas och parametrarna kommer att vara tillgängliga genomHttpServletRequest#getParameter()
.För att faktiskt avfyra HTTP-förfrågan
Du kan starta HTTP-förfrågan explicit med
URLConnection#connect()
, men förfrågan startas automatiskt på begäran när du vill få information om HTTP-svaret, t.ex. svarskroppen med hjälp avURLConnection#getInputStream()
och så vidare. Exemplen ovan gör exakt detta, så anropetconnect()
är faktiskt överflödigt.Samling av information om HTTP-svar
HttpURLConnection
här. Skapa den först om det behövs. int status = httpConnection.getResponseCode();HTTP-svarskodning: När
Content-Type
innehåller encharset
-parameter är svarskroppen troligen textbaserad och vi vill behandla svarskroppen med den teckenkodning som serversidan har angett. String contentType = connection.getHeaderField("Content-Type"); String charset = null; for (String param : contentType.replace(" " ", "").split(";")) { if (param.startsWith("charset=")) { charset = param.split("=", 2)1; break; } } if (charset != null) { försök (BufferedReader reader = new BufferedReader(new InputStreamReader(response, charset))) { for (String line; (line = reader.readLine()) != null;) { // ... System.out.println(line) ? } } } else { // Det är sannolikt binärt innehåll, använd InputStream/OutputStream. }Håller sessionen
Sessionen på serversidan stöds vanligtvis av en cookie. Vissa webbformulär kräver att du är inloggad och/eller spåras av en session. Du kan använda API:et
CookieHandler
för att hantera cookies. Du måste förbereda enCookieManager
med enCookiePolicy
påACCEPT_ALL
innan du skickar alla HTTP-förfrågningar.Observera att det är känt att detta inte alltid fungerar korrekt under alla omständigheter. Om det misslyckas för dig är det bäst att manuellt samla in och ställa in cookie-huvudena. Du måste i princip samla in alla
Set-Cookie
-huvuden från svaret på inloggningen eller den förstaGET
-förfrågan och sedan skicka detta genom de efterföljande förfrågningarna.split(";", 2)[0]
är till för att göra sig av med cookie-attribut som är irrelevanta för serversidan somexpires
,path
, etc. Alternativt kan du också användacookie.substring(0, cookie.indexOf(';'))
istället försplit()
.Streaming-läge
HttpURLConnection
kommer som standard att buffra den totala förfrågningskroppen innan den skickas, oavsett om du själv har satt en fast innehållslängd med hjälp avconnection.setRequestProperty("Content-Length", contentLength);
. Detta kan orsakaOutOfMemoryException
s när du samtidigt skickar stora POST-förfrågningar (t.ex. uppladdning av filer). För att undvika detta bör du ställa inHttpURLConnection#setFixedLengthStreamingMode()
.Men om innehållslängden verkligen inte är känd i förväg kan du använda dig av chunked streaming mode genom att ställa in
HttpURLConnection#setChunkedStreamingMode()
i enlighet med detta. Detta kommer att ställa in HTTP-huvudetTransfer-Encoding
tillchunked
, vilket gör att förfrågningskroppen skickas i bitar. Nedanstående exempel skickar kroppen i bitar på 1KB.User-Agent
Det kan hända att [en begäran returnerar ett oväntat svar, medan det fungerar bra med en riktig webbläsare] (https://stackoverflow.com/questions/13670692/403-forbidden-with-java-but-not-web-browser). Servern blockerar förmodligen förfrågningar baserat på
User-Agent
förfrågningshuvudet.URLConnection
kommer som standard att ställa in den påJava/1.6.0_19
där den sista delen uppenbarligen är JRE-versionen. Du kan åsidosätta detta på följande sätt:Använd User-Agent-strängen från en nyare webbläsare.
Felhantering
Om HTTP-svarskoden är
4nn
(Client Error) eller5nn
(Server Error) kan du läsaHttpURLConnection#getErrorStream()
för att se om servern har skickat någon användbar felinformation.Om HTTP-svarskoden är -1 är det något som gick fel med anslutningen och svarshanteringen. Implementationen av
HttpURLConnection
är i äldre JREs något buggig när det gäller att hålla anslutningar vid liv. Du kanske vill stänga av det genom att ställa in systemegenskapenhttp.keepAlive
tillfalse
. Du kan göra detta programmatiskt i början av din applikation genom att:Uppladdning av filer
Normalt använder du
multipart/form-data
kodning för blandat POST-innehåll (binära data och teckendata). Kodningen beskrivs närmare i RFC2388.Om den andra sidan är en
HttpServlet
, kommer dessdoPost()
metod att anropas och delarna kommer att vara tillgängliga genomHttpServletRequest#getPart()
(observera, alltså integetParameter()
och så vidare!). MetodengetPart()
är dock relativt ny, den introducerades i Servlet 3.0 (Glassfish 3, Tomcat 7, etc). Före Servlet 3.0 är det bästa valet att använda Apache Commons FileUpload för att analysera enmultipart/form-data
-förfrågan. Se även det här svaret för exempel på både FileUpload och Servelt 3.0.Hantering av opålitliga eller felkonfigurerade HTTPS-webbplatser
Ibland behöver du ansluta en HTTPS-URL, kanske för att du skriver en webscraper. I det fallet kan du sannolikt få ett
javax.net.ssl.SSLException: Not trusted server certificate
på vissa HTTPS-webbplatser som inte håller sina SSL-certifikat uppdaterade, eller ettjava.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found
ellerjavax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
på vissa felkonfigurerade HTTPS-webbplatser. Följandestatic
-initialiserare som körs en gång i din web scraper-klass bör göraHttpsURLConnection
mer eftergiven när det gäller dessa HTTPS-webbplatser och därmed inte längre kasta dessa undantag.Sista ord
Apache HttpComponents HttpClient är mycket bekvämare i detta sammanhang :)
HttpClient Examples
Parsing och extrahering av HTML
Om allt du vill göra är att analysera och extrahera data från HTML, är det bättre att använda en HTML-analysator som Jsoup.
När du arbetar med HTTP är det nästan alltid mer användbart att hänvisa till
HttpURLConnection
snarare än basklassenURLConnection
(eftersomURLConnection
är en abstrakt klass när du frågar efterURLConnection.openConnection()
på en HTTP-URL är det vad du får tillbaka ändå).Då kan du istället för att förlita dig på
URLConnection#setDoOutput(true)
för att implicit ställa in förfrågningsmetoden till POST istället görahttpURLConnection.setRequestMethod("POST")
vilket vissa kanske tycker är mer naturligt (och som också gör det möjligt för dig att specificera andra förfrågningsmetoder som PUT, DELETE, ...).Den tillhandahåller också användbara HTTP-konstanter så att du kan göra:
Inspirerad av denna och andra frågor på SO har jag skapat en minimal öppen källkod basic-http-client som innehåller de flesta av de tekniker som finns här.
google-http-java-client är också en bra öppen källkodresurs.