// 微盛 请求式API接口 实时行情 java demo 示例源码
// http://www.wstock.net  微盛投资 2015/10/12
//
// 编译: 请在命令行中输入,类似:javac wsGetHTML.java
// 编译后执行测试,请在命令行中输入,类似:java wsGetHTML
// 也支持带参数执行,例如 java wsGetHTML 网址 编码 存储文件名
//
// 微盛 请求式API接口,完整《开发文档》及说明,请参见:http://db2015.wstock.cn/wsDB_API/readme/
// 说明:本程序 演示 如何下载 网页 或 文件,支持gzip解压缩。可用于 处理 db2015.wstock.cn 中 微盛请求式API接口对应网页的下载、解压
// 参数1:URL,如果缺省,则默认为  http://db2015.wstock.cn/wsDB_API/stock.php?market=SH60&r_type=2
// 参数2:编码方式,例如 utf-8 (如果缺省,则从服务器端ContentType中获取;否则默认为 utf-8);
// 参数3:存储到文件(含路径名),例如 d:/temp/db2015.txt  如果缺省,则在命令行中直接输出,不存储
// http://www.wstock.net  微盛投资 2015/10/10
//
import java.net.URL;
import java.net.URLConnection;
import java.io.InputStream;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPInputStream;


public class wsGetHTML {
	static String ContentType="";	//实际下载得到的内容类型,来自 HTTP头部的 Content-Type,例如 Content-Type: text/plain; charset=utf-8
	static String ContentEncoding="";	//实际下载得到的内容类型,来自 HTTP头部的  Content-Encoding,例如 Content-Encoding: gzip

 	// 追加方式写入,将byte数组写入文件,从off偏移开始,写入len字节
	// 微盛投资 wstock.net 2015/10/13
	public static void AppendToFile(String filename, byte[] data, int off, int len) {		// throws IOException  {
		if (filename.length()>0 && len>0) {
			try {
				FileOutputStream fos = new FileOutputStream(filename,true);   // true:采用追加方式写入
				fos.write(data,off,len);		// 如文件存在,会自动追加写入到文件末尾
				fos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
 	// 获取网页编码方式,支持匹配 http头 或 html代码
	// 微盛投资 wstock.net 2015/10/10
	private static String getCharset(String str) {
		String s2="";

		// 先匹配 html 中的charset,类似:  <meta http-equiv='Content-Type' content='text/html; charset=gb2312'>
		Pattern pattern = Pattern.compile("(?s)^.*?charset *= *(.*?)[ ?\'?\"?>?\r\n].*?$",Pattern.CASE_INSENSITIVE);
		Matcher matcher = pattern.matcher(str);
		if ( matcher.find() ) {
			s2=matcher.replaceAll("$1");
			if (s2 != null) if (s2.length()>1 ) { return s2;}
		}

		// 然后匹配 http header中的charset,类似:Content-Type: text/plain; charset=utf-8
		pattern = Pattern.compile("(?s)^.*?charset *= *(.*?)$",Pattern.CASE_INSENSITIVE);
		matcher = pattern.matcher(str);
		if ( matcher.find() ) {	s2=matcher.replaceAll("$1"); return s2; }
		
		return null;
	 }
	 
	//  解压gzip数据,data就是接收到的数据;返回解压缩后的数据(字节数组)
	public static byte[] decompressGzip(byte[] data) {
		GZIPInputStream gis = null;
		try {
				ByteArrayInputStream bais = new ByteArrayInputStream(data);
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				gis = new GZIPInputStream(bais);
				int count;
				byte data2[] = new byte[data.length];
				while ((count = gis.read(data2, 0, data.length)) != -1) {
				    baos.write(data2, 0, count);
				}
				gis.close();	  data = baos.toByteArray();
				baos.flush();	baos.close();	bais.close();            //         System.out.println("解压成功");
		} catch (IOException e) {
			e.printStackTrace();		//System.out.println(ex);           
		} finally {
			try {
				gis.close();
		} catch (IOException e) {
				e.printStackTrace();// System.out.println(ex);
            }
        }
        return data;
    }

	// 通过字节数组下载网页,这种方法更灵活,支持下载(抓取)图片、文件,以及压缩格式gzip的网页
	// 这里通过java的 ByteArrayOutputStream 来作为缓存,代码会简单些(不用自己管理缓存大小)
	// 返回下载的数据
	// 微盛投资 wstock.net 2015/10/12
	public static byte[] wsGetHTMLByByte(String strURL){
		ByteArrayOutputStream baos = new ByteArrayOutputStream();		// 初始化缓存
		
		try{
			URL url = new URL(strURL);
			URLConnection conn = url.openConnection();
			conn.setConnectTimeout(10*1000);				// 设置超时间为10秒
			conn.setRequestProperty("User-Agent", "wstock java demo:wsGetHTMLByByte");   //告诉服务器,自己程序的名称。 在一些特殊应用中,可设置为浏览器名称(一般不用)。例如 Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0
			
			InputStream in = conn.getInputStream();		// 输入流
			byte[] bs = new byte[10240];			// 每次最多读取10KB
			int len = 0;	int total_size=0;	int total_len=0;
			total_len=conn.getContentLength();		// getContentLength 实际来自 HTTP头部的 content-length ,对于动态输出页面,WebServer未必包括content-length
			ContentType = conn.getContentType();		// 例如:Content-Type: text/plain; charset=utf-8
			ContentEncoding=conn.getContentEncoding();			// 例如:Content-Encoding: gzip
			System.out.println("ContentType:" + ContentType);
			System.out.println("ContentEncoding from HTTP Header:" + ContentEncoding);
			System.out.println("content-length:" + total_len);

			//循环判断,如果bs为空,则in.read()方法返回-1,具体请参考InputStream的read();
			while ((len = in.read(bs)) >0 ) { 			// while ((len = in.read(bs)) != -1) {
				baos.write(bs, 0, len);					// 将新收到的数据放入缓存baos尾部, java的 ByteArrayOutputStream 会自动管理缓存大小
				total_size+=len;	 System.out.println("downloading:" + total_size + "/" + total_len );   // 显示下载进度
			}
			in.close();			// System.out.println("下载成功");
			if (total_size>0) {byte[] data = baos.toByteArray();	baos.flush();	baos.close();	return data;} else {return null;}
		}catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}


	// 参数1:URL,如果缺省,则默认为  http://db2015.wstock.cn/wsDB_API/stock.php?market=SH60&r_type=2
	// 参数2:编码方式,例如 utf-8 (如果缺省,则默认为服务器端返回的charset,否则默认为 utf-8)
	// 参数3:存储到文件(含路径名),例如 d:/temp/db2015.txt  如果缺省,则在命令行中直接输出,不存储
	// 微盛投资 wstock.net  2015/10/09
	public static void main(String args[]){
		String tURL="http://db2015.wstock.cn/wsDB_API/stock.php?market=SH60&r_type=2";	// 参数1:URL,如果缺省,则默认
		String encoding="a";		// utf-8
		String filename="";
		int i=0;
		
		if (args.length>=1) { if (args[0].length()>1) { tURL=args[0];} }
		if (args.length>=2) { if (args[1].length()>1) { encoding=args[1];} }
		if (args.length>=3) {filename=args[2];}

		if (tURL.toLowerCase().indexOf("http")<0) {tURL="http://"+tURL;}		// 增加默认协议头
		System.out.println("URL:" + tURL);
		byte[] html_buffer=wsGetHTMLByByte(tURL);			// 下载文件 或者 获取对应网页的数据
		if (html_buffer==null) {System.out.println("未能下载到文件,退出"); System.exit(-1);}
		
		// gzip解压缩
		String str=ContentEncoding;
		if ( str != null) {
			if ( str.toLowerCase().indexOf("gzip")>=0 ) {		// 是gzip压缩方式,需要先解压缩
				html_buffer=decompressGzip(html_buffer);		// 解压缩
				System.out.println("gzip unzip OK.");
			}
		}
		// gzip解压缩

		// 提取 编码方式
		str=ContentType;
		if (str!=null ) {
			if ( encoding.length()<2 ) { str=getCharset(str); if (str!=null ) { encoding=str;	} }  // 如果用户没有手动指定编码方式,则尝试从服务器端返回Header数据中提取 charset
		} 
		if ( encoding.length()<2 ) {		// 尝试从数据中提取 字符集,例如 html中的 
			i=1024; if (i>html_buffer.length) {i=html_buffer.length;} 	str = new String(html_buffer, 0, i);		// 这里将 字节数组最多前面1K字节 转化为 字符串,直接按默认字符集转化,以便提取charset
			if (str!=null ) { str=getCharset(str); if (str!=null ) { encoding=str;	} }
		}
		if ( encoding.length()<2 ) { encoding="utf-8"; }				// 如果无法正确提取到编码方式,则默认为utf-8
		System.out.println("encoding:" + encoding + ";");
		// 提取 编码方式

		if (filename.length()>0 && html_buffer.length > 0) {		// 存储到文件
			AppendToFile(filename,html_buffer,0,html_buffer.length);		// 原始数据追加存储到文件,这个是二进制原始数据,因此不必判断 编码方式,直接存储即可
		} else {			// 显示下载的内容
			try {
				str = new String(html_buffer, 0, html_buffer.length, encoding);		// 这里将 字节数组 转化为 字符串,必须指定正确的编码方式(encoding)
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			System.out.println(str);
		}
	}
}

// [END]
// [全文完]
// 微盛投资  wstock.net  2015/10/12