I need to post data to server (with "referer" header field) and load the response in Webview.
Now, there are different methods (from Android WebView) to do parts of it, like there is:
void loadUrl(String url, Map<String, String> additionalHttpHeaders)
Loads the given URL with the specified additional HTTP headers.
void loadData(String data, String mimeType, String encoding)
Loads the given data into this WebView using a 'data' scheme URL.
void postUrl(String url, byte[] postData)
Loads the URL with postData using "POST" method into this WebView.
loadUrl() allows to send HttpHeaders but doesn't allow to send post data, other methods seem to be not allowing to send HttpHeaders. Am I missing something or what I am trying is not possible?
You can execute the HttpPost manually like this:
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://www.yoursite.com/postreceiver");
// generating your data (AKA parameters)
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("ParameterName", "ParameterValue"));
// ...
// adding your headers
httppost.setHeader("HeaderName", "HeaderValue");
// ...
// adding your data
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
Get the response as String:
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
StringBuilder builder = new StringBuilder();
for (String line = null; (line = reader.readLine()) != null;) {
builder.append(line).append("\n");
}
String html = builder.toString();
Now you can put the html into yourWebView by using loadData():
yourWebView.loadData(html ,"text/html", "UTF-8");
You can use a custom class that inherits from WebView, or if you prefer, you can add an extension function. The logic is essentially the same:
private fun WebView.postUrl(postUrl: String, postData: ByteArray, additionalHttpHeaders: MutableMap<String, String>) {
val savedWebViewClient = getWebViewClient()
webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
if (url != postUrl) {
view.post {
webViewClient = savedWebViewClient
}
return savedWebViewClient?.shouldInterceptRequest(view, url)
}
Log.d("WebView extension", "post ${postData.decodeToString()} to ${url}")
val httpsUrl = URL(url)
val conn: HttpsURLConnection = httpsUrl.openConnection() as HttpsURLConnection
conn.requestMethod = "POST"
additionalHttpHeaders.forEach { header ->
conn.addRequestProperty(header.key, header.value)
}
conn.outputStream.write(postData)
conn.outputStream.close()
val responseCode = conn.responseCode
Log.d("WebView extension", "responseCode = ${responseCode} ${conn.contentType}")
view.post {
webViewClient = savedWebViewClient
}
// typical conn.contentType is "text/html; charset=UTF-8"
return WebResourceResponse(conn.contentType.substringBefore(";"), "utf-8", conn.inputStream)
}
}
loadUrl(postUrl, additionalHttpHeaders)
}
The code above was redacted for brevity, with most error checking hidden. To work for API below level 26, I use reflection to extract the savedWebViewClient.
In real life, you also want to override the new shouldInterceptRequest(view: WebView, request: WebResourceRequest) method, and delegate all other methods of your WebViewClient to the savedWebViewClient. Probably a PostWithHeadersWebView class (which overrides also setWibViewClient()) could make your life easier.
Related
I need to post data to server (with "referer" header field) and load the response in Webview.
Now, there are different methods (from Android WebView) to do parts of it, like there is:
void loadUrl(String url, Map<String, String> additionalHttpHeaders)
Loads the given URL with the specified additional HTTP headers.
void loadData(String data, String mimeType, String encoding)
Loads the given data into this WebView using a 'data' scheme URL.
void postUrl(String url, byte[] postData)
Loads the URL with postData using "POST" method into this WebView.
loadUrl() allows to send HttpHeaders but doesn't allow to send post data, other methods seem to be not allowing to send HttpHeaders. Am I missing something or what I am trying is not possible?
You can execute the HttpPost manually like this:
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://www.yoursite.com/postreceiver");
// generating your data (AKA parameters)
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("ParameterName", "ParameterValue"));
// ...
// adding your headers
httppost.setHeader("HeaderName", "HeaderValue");
// ...
// adding your data
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
Get the response as String:
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
StringBuilder builder = new StringBuilder();
for (String line = null; (line = reader.readLine()) != null;) {
builder.append(line).append("\n");
}
String html = builder.toString();
Now you can put the html into yourWebView by using loadData():
yourWebView.loadData(html ,"text/html", "UTF-8");
You can use a custom class that inherits from WebView, or if you prefer, you can add an extension function. The logic is essentially the same:
private fun WebView.postUrl(postUrl: String, postData: ByteArray, additionalHttpHeaders: MutableMap<String, String>) {
val savedWebViewClient = getWebViewClient()
webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
if (url != postUrl) {
view.post {
webViewClient = savedWebViewClient
}
return savedWebViewClient?.shouldInterceptRequest(view, url)
}
Log.d("WebView extension", "post ${postData.decodeToString()} to ${url}")
val httpsUrl = URL(url)
val conn: HttpsURLConnection = httpsUrl.openConnection() as HttpsURLConnection
conn.requestMethod = "POST"
additionalHttpHeaders.forEach { header ->
conn.addRequestProperty(header.key, header.value)
}
conn.outputStream.write(postData)
conn.outputStream.close()
val responseCode = conn.responseCode
Log.d("WebView extension", "responseCode = ${responseCode} ${conn.contentType}")
view.post {
webViewClient = savedWebViewClient
}
// typical conn.contentType is "text/html; charset=UTF-8"
return WebResourceResponse(conn.contentType.substringBefore(";"), "utf-8", conn.inputStream)
}
}
loadUrl(postUrl, additionalHttpHeaders)
}
The code above was redacted for brevity, with most error checking hidden. To work for API below level 26, I use reflection to extract the savedWebViewClient.
In real life, you also want to override the new shouldInterceptRequest(view: WebView, request: WebResourceRequest) method, and delegate all other methods of your WebViewClient to the savedWebViewClient. Probably a PostWithHeadersWebView class (which overrides also setWibViewClient()) could make your life easier.
I'm developping an Android application filtering the requests (with a white list) and using a custom SSLSocketFactory. For this, I've developed a custom WebViewClient and I have overridden the shouldInterceptRequest method. I can filter and use my SocketFactory with the GET requests but I can't intercept the POST requests.
So, is there a way to intercept the POST requests in a WebView ?
Here is the code of the shouldInterceptRequest method :
public final WebResourceResponse shouldInterceptRequest(WebView view, String urlStr) {
URI uri = URI.create(urlStr);
String scheme = uri.getScheme();
// If scheme not http(s), let the default webview manage it
if(!"http".equals(scheme) && !"https".equals(scheme)) {
return null;
}
URL url = uri.toURL();
if(doCancelRequest(url)) {
// Empty response
Log.d(TAG, "URL filtered: " + url);
return new WebResourceResponse("text/plain", "UTF-8", new EmptyInputStream());
} else {
Log.d(TAG, "URL: " + url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", mSettings.getUserAgentString());
// Configure connections
configureConnection(conn);
String mimeType = conn.getContentType();
String encoding = conn.getContentEncoding();
if(mimeType != null && mimeType.contains(CONTENT_TYPE_SPLIT)) {
String[] split = mimeType.split(CONTENT_TYPE_SPLIT);
mimeType = split[0];
Matcher matcher = CONTENT_TYPE_PATTERN.matcher(split[1]);
if(matcher.find()) {
encoding = matcher.group(1);
}
}
InputStream is = conn.getInputStream();
return new WebResourceResponse(mimeType, encoding, is);
}
}
I was facing the same issue a few days ago.
So I built a library that solves it:
https://github.com/KonstantinSchubert/request_data_webviewclient
It is a WebViewClient with a custom WebResourceRequest that contains the POST/PUT/... payload of XMLHttpRequest requests.
It only works for these though - not for forms and other kind of request sources.
The hack works, basically, by injecting a script into the HTML that intercepts XMLHttpRequest calls. It records the post/put/... content and sends it to an android.webkit.JavascriptInterface. There, the request is stashed until the shouldInterceptRequest method is called by Android ...
I have created a library that aims to capture all data of all HTTP requests sent from Android WebViews.
Using this library, you can easily implement sending POST, or any other requests. Here's your code adapted to work with the library:
#Nullable
#Override
public final WebResourceResponse shouldInterceptRequest(WebView view, WebViewRequest request) {
URI uri = URI.create(request.getUrl());
String scheme = uri.getScheme();
// If scheme not http(s), let the default webview manage it
if(!"http".equals(scheme) && !"https".equals(scheme)) {
return null;
}
URL url = uri.toURL();
if(doCancelRequest(url)) {
// Empty response
Log.d(TAG, "URL filtered: " + url);
return new WebResourceResponse("text/plain", "UTF-8", new EmptyInputStream());
} else {
Log.d(TAG, "URL: " + url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set request method
conn.setRequestMethod(request.getMethod());
// Set request headers
for (Map.Entry<String, String> header : request.getHeaders().entrySet()) {
conn.setRequestProperty(header.getKey(), header.getValue());
}
// Set request body, if it's present
if (!request.getBody().isEmpty()) {
OutputStream os = conn.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
osw.write(request.getBody());
osw.flush();
osw.close();
os.close(); //don't forget to close the OutputStream
}
conn.connect();
String mimeType = conn.getContentType();
String encoding = conn.getContentEncoding();
if(mimeType != null && mimeType.contains(CONTENT_TYPE_SPLIT)) {
String[] split = mimeType.split(CONTENT_TYPE_SPLIT);
mimeType = split[0];
Matcher matcher = CONTENT_TYPE_PATTERN.matcher(split[1]);
if(matcher.find()) {
encoding = matcher.group(1);
}
}
InputStream is = conn.getInputStream();
return new WebResourceResponse(mimeType, encoding, is);
}
}
I have one of my answers on above thread http://code.google.com/p/android/issues/detail?id=9122
Please see comment#31
Some of the caveats of my solution I see are:
Putting a dependency on xmlhttprequest prototype which has different implementation for different webkits.
Security issue in sending data for post requests in URL. But I guess you can solve that through some encryption mechanism.
URL length issue for some of the browsers if you big data to post
Apart from that, I found this github repo which seems to be solving this problem in another hacky way. I looked into the code but didn't get time to implement and test it. But worth giving a try.
The easiest way I found to do this is to just use the JQuery Ajax Event Handlers with a Javascript Interface.
You have to setup the Javascript Interface, but once you do just have it match the signature of the handler you're interested in.
To get the payload, just find the payload variable name put it in the interface.
Here's the Javascript:
$( document ).ajaxSend(function( event, request, settings ) {
var eventJSON= JSON.stringify(event);
var requestJSON = JSON.stringify(request);
var settingsJSON = JSON.stringify(settings);
var payloadJSON = JSON.stringify(payload);
myJSInterface.passData(eventJSON,requestJSON,settingsJSON,payloadJSON);
});
Here's the Kotlin class
class yourJSInterface() {
lateinit var event: String
lateinit var request: String
lateinit var settings: String
lateinit var payload: String
#JavascriptInterface
fun passData(eventJSON:String, responseJSON:String, settingsJSON:String,payloadJSON:String){
event = eventJSON
response= responseJSON
settings= settingsJSON
payload= payloadJSON
}
}
The registration in the onPageStarted override for WebViewClient
webView.addJavascriptInterface(yourJSInterface,"myJSInterface")
Finally the JS injection into the WebView in the OnPageFinished override
webView.evaluateJavascript("javascript:" + getString(R.string.js_from_above),null)
I registered the interface in onPageStarted because otherwise, the javascript file in onPageFinished won't recognize your interface.
There's a simpler solution: send the parameters by GET in the ajax call and transform them to POST in shouldInterceptRequest
you can get input value before submit
https://github.com/henrychuangtw/WebView-Javascript-Inject
Step 1 : create a class which called by javascript
class MyJavaScriptInterface
{
#JavascriptInterface
public void processHTML(String html)
{
//called by javascript
}
}
Step 2 : register interface for javascript
webview1.getSettings().setJavaScriptEnabled(true);
webview1.addJavascriptInterface(new MyJavaScriptInterface(), "MYOBJECT");
Step 3 : inject javascript to page
webview1.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
StringBuilder sb = new StringBuilder();
sb.append("document.getElementsByTagName('form')[0].onsubmit = function () {");
sb.append("var objPWD, objAccount;var str = '';");
sb.append("var inputs = document.getElementsByTagName('input');");
sb.append("for (var i = 0; i < inputs.length; i++) {");
sb.append("if (inputs[i].type.toLowerCase() === 'password') {objPWD = inputs[i];}");
sb.append("else if (inputs[i].name.toLowerCase() === 'email') {objAccount = inputs[i];}");
sb.append("}");
sb.append("if (objAccount != null) {str += objAccount.value;}");
sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}");
sb.append("window.MYOBJECT.processHTML(str);");
sb.append("return true;");
sb.append("};");
view.loadUrl("javascript:" + sb.toString());
}
});
Use GET instead of POST.
Known issue:
http://code.google.com/p/android/issues/detail?id=9122
Was answered here as well:
Android - how to intercept a form POST in android WebViewClient on API level 4
I want to make an http post request using webview.
webView.setWebViewClient(new WebViewClient(){
public void onPageStarted(WebView view, String url,
Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
public boolean shouldOverrideUrlLoading(WebView view,
String url) {
webView.postUrl(Base_Url, postData.getBytes());
return true;
}
});
The above code snippet loads the webpage. I want to access the response of this request.
How can i obtain the response of an http post request using webview?
Thanks in Advance
First add the support of the http library to your gradle file:
To be able to use
useLibrary 'org.apache.http.legacy'
After this you can use the following code to perform post request in your webview:
public void postUrl (String url, byte[] postData)
String postData = "submit=1&id=236";
webview.postUrl("http://www.belencruzz.com/exampleURL",EncodingUtils.getBytes(postData, "BASE64"));
http://belencruz.com/2012/12/do-post-request-on-a-webview-in-android/
The WebView does not let you access the content of the HTTP response.
You have to use HttpClient for that, and then forward the content to the view by using the function loadDataWithBaseUrl and specifying the base url so that the user can use the webview to continue navigating in the website.
Example:
// Executing POST request
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
httppost.setEntity(postContent);
HttpResponse response = httpclient.execute(httppost);
// Get the response content
String line = "";
StringBuilder contentBuilder = new StringBuilder();
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
while ((line = rd.readLine()) != null) {
contentBuilder.append(line);
}
String content = contentBuilder.toString();
// Do whatever you want with the content
// Show the web page
webView.loadDataWithBaseURL(url, content, "text/html", "UTF-8", null);
I need to post data to server (with "referer" header field) and load the response in Webview.
Now, there are different methods (from Android WebView) to do parts of it, like there is:
void loadUrl(String url, Map<String, String> additionalHttpHeaders)
Loads the given URL with the specified additional HTTP headers.
void loadData(String data, String mimeType, String encoding)
Loads the given data into this WebView using a 'data' scheme URL.
void postUrl(String url, byte[] postData)
Loads the URL with postData using "POST" method into this WebView.
loadUrl() allows to send HttpHeaders but doesn't allow to send post data, other methods seem to be not allowing to send HttpHeaders. Am I missing something or what I am trying is not possible?
You can execute the HttpPost manually like this:
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://www.yoursite.com/postreceiver");
// generating your data (AKA parameters)
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("ParameterName", "ParameterValue"));
// ...
// adding your headers
httppost.setHeader("HeaderName", "HeaderValue");
// ...
// adding your data
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpclient.execute(httppost);
Get the response as String:
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
StringBuilder builder = new StringBuilder();
for (String line = null; (line = reader.readLine()) != null;) {
builder.append(line).append("\n");
}
String html = builder.toString();
Now you can put the html into yourWebView by using loadData():
yourWebView.loadData(html ,"text/html", "UTF-8");
You can use a custom class that inherits from WebView, or if you prefer, you can add an extension function. The logic is essentially the same:
private fun WebView.postUrl(postUrl: String, postData: ByteArray, additionalHttpHeaders: MutableMap<String, String>) {
val savedWebViewClient = getWebViewClient()
webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
if (url != postUrl) {
view.post {
webViewClient = savedWebViewClient
}
return savedWebViewClient?.shouldInterceptRequest(view, url)
}
Log.d("WebView extension", "post ${postData.decodeToString()} to ${url}")
val httpsUrl = URL(url)
val conn: HttpsURLConnection = httpsUrl.openConnection() as HttpsURLConnection
conn.requestMethod = "POST"
additionalHttpHeaders.forEach { header ->
conn.addRequestProperty(header.key, header.value)
}
conn.outputStream.write(postData)
conn.outputStream.close()
val responseCode = conn.responseCode
Log.d("WebView extension", "responseCode = ${responseCode} ${conn.contentType}")
view.post {
webViewClient = savedWebViewClient
}
// typical conn.contentType is "text/html; charset=UTF-8"
return WebResourceResponse(conn.contentType.substringBefore(";"), "utf-8", conn.inputStream)
}
}
loadUrl(postUrl, additionalHttpHeaders)
}
The code above was redacted for brevity, with most error checking hidden. To work for API below level 26, I use reflection to extract the savedWebViewClient.
In real life, you also want to override the new shouldInterceptRequest(view: WebView, request: WebResourceRequest) method, and delegate all other methods of your WebViewClient to the savedWebViewClient. Probably a PostWithHeadersWebView class (which overrides also setWibViewClient()) could make your life easier.
Is there a way to view the http response headers in an Activity once a web page has been loaded in a WebView? Seems like this should be possible, but I can't find any methods that expose the headers.
Neither WebView nor WebViewClient provide methods to do that, Though, you can try to implement that manually. You can do something like this:
private WebView webview;
public void onCreate(Bundle icicle){
// bla bla bla
// here you initialize your webview
webview = new WebView(this);
webview.setWebViewClient(new YourWebClient());
}
// this will be the webclient that will manage the webview
private class YourWebClient extends WebViewClient{
// you want to catch when an URL is going to be loaded
public boolean shouldOverrideUrlLoading (WebView view, String urlConection){
// here you will use the url to access the headers.
// in this case, the Content-Length one
URL url;
URLConnection conexion;
try {
url = new URL(urlConection);
conexion = url.openConnection();
conexion.setConnectTimeout(3000);
conexion.connect();
// get the size of the file which is in the header of the request
int size = conexion.getContentLength();
}
// and here, if you want, you can load the page normally
String htmlContent = "";
HttpGet httpGet = new HttpGet(urlConection);
// this receives the response
HttpResponse response;
try {
response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() == 200) {
// la conexion fue establecida, obtener el contenido
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream inputStream = entity.getContent();
htmlContent = convertToString(inputStream);
}
}
} catch (Exception e) {}
webview.loadData(htmlContent, "text/html", "utf-8");
return true;
}
public String convertToString(InputStream inputStream){
StringBuffer string = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
try {
while ((line = reader.readLine()) != null) {
string.append(linea + "\n");
}
} catch (IOException e) {}
return string.toString();
}
}
I can't test it right now, but that's basically what you could do (it's very crazy though :).
inspired by Cristian answer I needed to intercept AJAX calls webview is doing, where I needed to intercept response headers to get some information (cart item count in e-commerce app), which I needed to leverage in app. As the app is using okhttp I've ended up doing this and it's working:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
Log.i(TAG,"shouldInterceptRequest path:"+request.getUrl().getPath());
WebResourceResponse returnResponse = null;
if (request.getUrl().getPath().startsWith("/cart")) { // only interested in /cart requests
returnResponse = super.shouldInterceptRequest(view, request);
Log.i(TAG,"cart AJAX call - doing okRequest");
Request okRequest = new Request.Builder()
.url(request.getUrl().toString())
.post(null)
.build();
try {
Response okResponse = app.getOkHttpClient().newCall(okRequest).execute();
if (okResponse!=null) {
int statusCode = okResponse.code();
String encoding = "UTF-8";
String mimeType = "application/json";
String reasonPhrase = "OK";
Map<String,String> responseHeaders = new HashMap<String,String>();
if (okResponse.headers()!=null) {
if (okResponse.headers().size()>0) {
for (int i = 0; i < okResponse.headers().size(); i++) {
String key = okResponse.headers().name(i);
String value = okResponse.headers().value(i);
responseHeaders.put(key, value);
if (key.toLowerCase().contains("x-cart-itemcount")) {
Log.i(TAG,"setting cart item count");
app.setCartItemsCount(Integer.parseInt(value));
}
}
}
}
InputStream data = new ByteArrayInputStream(okResponse.body().string().getBytes(StandardCharsets.UTF_8));
Log.i(TAG, "okResponse code:" + okResponse.code());
returnResponse = new WebResourceResponse(mimeType,encoding,statusCode,reasonPhrase,responseHeaders,data);
} else {
Log.w(TAG,"okResponse fail");
}
} catch (IOException e) {
e.printStackTrace();
}
}
return returnResponse;
}
I hope this may be helpful to others and if somebody has a suggestions for improvement I would be grateful. Unfortunately it's compatible only with LOLLIPOP and higher as from this version you can access/return headers using WebResourceRequest, which was needed for my case.
You should be able to control all your headers by skipping loadUrl and writing your own loadPage using Java's HttpURLConnection. Then view the headers, do your thing, and use the webview's loadData to display the response.
There is an alternative solution if you're targeting at least Kit-Kat, even though this wouldn't show the headers in the Activity but rather in Chrome. You can simply follow this short guide on how to remotely debug Webviews.
The 2 key points are, first, to enable WebView debugging in you app
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
And then open chrome://inspect in a Chrome tab in a computer. Once you connect your phone via USB to the computer, you will see your app's WebView in the list of debuggable devices
you can use OkHttp:
private fun handleRequestViaOkHttp(url: String) {
var httpClient = OkHttpClient()
thread {
try {
val request = Request.Builder().url(url).build()
print("Request: $request")
val response = httpClient.newCall(request).execute()
println("Response: " + response.headers().toString())
} catch (e: Exception) {}
}
}
you should call it inside this method:
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
handleRequestViaOkHttp(webViewUrl.value.toString())
return super.shouldInterceptRequest(view, request)
}
As the accepted answer will only work with HttpGet, here is a trick thet currently I'm using (at this time it seems to work)
In onPageFinished handler, if there is an error, the title of the page will be like "ERROR_NUM - ERROR_DESCRIPTION", like "500 - Internal Server Error", so all I do is to get title from webview in the function, and then check the title.
view.getTitle()