package com.taobao.top.analysis; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.taobao.top.analysis.node.IJobManager; import com.taobao.top.analysis.util.ExtFilenameFilter; /** * * @author zijiang.jl * * * 分布式http节点,提供分析器与外部应用的通道,基于http协议。 * * * * 1.健康检查 * * http://url/web?command=check * * * 2.运行时内存数据管理 * * * http://url/web?command=manage&instance=in&method=mh&entry=en&key=k&value=val * * * method 支持: * get 获取数据 * put 创建数据 * post 修改数据 * delete 删除数据 * * * 3.属性文件和配置文件管理 * * http://url/web?command=config&instance=in&type=0&method=get * * type: * 0 属性文件 * 1 规则文件 * * method: * get 获取文件 * post 推送文件 * * */ public class HttpAgentNode extends Thread { private static final Log logger = LogFactory.getLog(HttpAgentNode.class); private static final String COMMAND="command"; private static final String COMMAND_CHECK="check"; private static final String COMMAND_CONFIG="config"; private static final String COMMAND_MANAGE="manage"; private static final String METHOD="method"; private static final String TYPE="type"; private static final String INSTANCE="instance"; private static final String LEVEL="level"; private static final String ENTRY="entry"; private static final String ALLENTRY="a_check_for_all_reference_entry"; public static final String _DEFAULT_REGISTER_ = "_DEFAULT_RULE_REGISTRY_"; private IJobManager jobManager; /** * @param jobManager the jobManager to set */ public void setJobManager(IJobManager jobManager) { this.jobManager = jobManager; } private static String ip; static { try { ip = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { } } interface Callback{ public void callback(int code,long len) throws IOException; } @Override public void run() { try { Thread.sleep(2000); logger.error("jobManager:" + jobManager + ", config:" + jobManager.getConfig()); HttpServer httpServer = HttpServer.create( new InetSocketAddress(jobManager.getConfig().getHttpPort()), 0); httpServer.createContext( jobManager.getConfig().getHttpContext(), new HttpHandler() { private AtomicInteger checkCnt=new AtomicInteger(0); private AtomicInteger configCnt=new AtomicInteger(0); @Override public void handle(final HttpExchange arg0)throws IOException { try{ String qu=arg0.getRequestURI().getQuery(); if(qu==null) qu="command=check"; java.util.Map<String, String> quMap=new java.util.HashMap<String, String>(); StringBuilder sb = new StringBuilder(); StringTokenizer st = new StringTokenizer(qu, "&"); while (st.hasMoreTokens()) { String pair = st.nextToken(); int pos = pair.indexOf('='); if (pos != -1) { String key = parseName(pair.substring(0, pos), sb); String val = parseName(pair.substring(pos+1, pair.length()), sb); quMap.put(key, java.net.URLDecoder.decode(val,"UTF-8")); } } String command=quMap.get(COMMAND); if(command==null||"".equals(command)) command=COMMAND_CHECK; logger.warn("http agent node accept command "+command); java.lang.StringBuilder exetime=new StringBuilder("the command:"); exetime.append(command); exetime.append(" execute time:"); if(COMMAND_CHECK.equals(command)){ long start=System.currentTimeMillis(); handleCheck(arg0.getRequestBody(),arg0.getResponseBody(),new Callback(){ @Override public void callback(int code,long len) throws IOException{ arg0.sendResponseHeaders(code, len); } }); long end=System.currentTimeMillis(); checkCnt.incrementAndGet(); exetime.append(end-start); exetime.append(",execute count:"); exetime.append(checkCnt.get()); logger.warn(exetime.toString()); return; } if(COMMAND_MANAGE.equals(command)){ long start=System.currentTimeMillis(); String method=quMap.get(METHOD); String level=quMap.get(LEVEL); String instance=quMap.get(INSTANCE); String entry=quMap.get(ENTRY); String key=quMap.get("key"); String value=quMap.get("value"); handleManage(arg0.getRequestBody(),arg0.getResponseBody(),new Callback(){ @Override public void callback(int code,long len) throws IOException{ arg0.sendResponseHeaders(code, len); } },method,level,instance,entry,key,value); long end=System.currentTimeMillis(); exetime.append(end-start); exetime.append(",execute count:"); logger.warn(exetime.toString()); return; } if(COMMAND_CONFIG.equals(command)){ long start=System.currentTimeMillis(); String instance=quMap.get(INSTANCE); String type=quMap.get(TYPE); String method=quMap.get(METHOD); handleConfig(arg0.getRequestBody(),arg0.getResponseBody(),new Callback(){ @Override public void callback(int code,long len) throws IOException{ arg0.sendResponseHeaders(code, len); } },type,method,instance); long end=System.currentTimeMillis(); configCnt.incrementAndGet(); exetime.append(end-start); exetime.append(",execute count:"); exetime.append(configCnt.get()); logger.warn(exetime.toString()); return; } logger.error("http agent node command:"+command+" was not supported"); }catch(Throwable t){ logger.error("http handle error", t); } } }); httpServer.start(); } catch (Throwable e) { logger.error("nested http server error", e); } } /** * //未做并发保护,谨慎使用 * @param arg0 * @param method * @param instance * @param entry * @param key * @param value */ private void handleManage(InputStream is,OutputStream os,Callback callback,String method,String level,String instance,String entry,String key,String value){ StringBuilder node = new StringBuilder(""); try { if(instance!=null){ if(jobManager.getJobs().get(instance)!=null){ if("get".equals(method)){ if(entry!=null){ if(jobManager.getJobs().get(instance).getJobResult().get(entry)!=null){ if(key!=null){ node.append(jobManager.getJobs().get(instance).getJobResult().get(key)); }else{ Map<String, Object> map=jobManager.getJobs().get(instance).getJobResult().get(entry); java.util.Iterator<String> it=map.keySet().iterator(); while(it.hasNext()){ node.append(it.next()); node.append(","); } } }else{ node.append("not find entry "+entry+" in memory"); node.append("\r\n"); callback.callback(415,node.toString().getBytes("UTF-8").length); os.write(node.toString().getBytes("UTF-8")); os.flush(); os.close(); return; } }else{ java.util.Iterator<String> it=jobManager.getJobs().get(instance).getJobResult().keySet().iterator(); node.append(ALLENTRY).append(","); while(it.hasNext()){ node.append(it.next()); node.append(","); } } } else if("put".equals(method)){ if(jobManager.getJobs().get(instance).getJobResult().get(entry)==null) jobManager.getJobs().get(instance).getJobResult().put(entry, new java.util.HashMap<String, Object>()); if(key!=null&&value!=null) jobManager.getJobs().get(instance).getJobResult().get(entry).put(key, value); } else if("post".equals(method)){ if(entry!=null&&key!=null&&value!=null&&jobManager.getJobs().get(instance).getJobResult().get(entry)!=null) jobManager.getJobs().get(instance).getJobResult().get(entry).put(key, value); }else if("delete".equals(method)){ if("1".equals(level)){ Map<String, Map<String, Object>> m=jobManager.getJobs().get(instance).getJobResult(); if(m!=null) m.clear(); } if("2".equals(level)){ Map<String, Map<String, Object>> m=jobManager.getJobs().get(instance).getJobResult(); if(m!=null){ if(ALLENTRY.equals(entry)){ java.util.Iterator<String> it=m.keySet().iterator(); while(it.hasNext()){ String st=it.next(); if(jobManager.getJobs().get(instance).getStatisticsRule().getEntryPool().containsKey(st)) continue; Map<String, Object> b=m.remove(st); if(b!=null) b.clear(); } }else{ Map<String, Object> b=m.remove(entry); if(b!=null) b.clear(); } } } if("3".equals(level)){ Map<String, Map<String, Object>> m=jobManager.getJobs().get(instance).getJobResult(); if(m!=null){ Map<String, Object> b=m.get(entry); if(b!=null) b.remove(key); } } }else{ node.append("not support!method error"); node.append("\r\n"); callback.callback(415,node.toString().getBytes("UTF-8").length); os.write(node.toString().getBytes("UTF-8")); os.flush(); os.close(); return; } }else{ node.append("not find instance "+instance+" in memory"); node.append("\r\n"); callback.callback(415,node.toString().getBytes("UTF-8").length); os.write(node.toString().getBytes("UTF-8")); os.flush(); os.close(); return; } }else{ if(jobManager != null && jobManager.getConfig() != null){ java.util.Iterator<String> in=jobManager.getJobs().keySet().iterator(); while(in.hasNext()){ node.append(in.next()); node.append(","); } }else{ node.append(_DEFAULT_REGISTER_); } } callback.callback(HttpServletResponse.SC_OK,node.toString().getBytes("UTF-8").length); os.write(node.toString().getBytes("UTF-8")); os.flush(); os.close(); } catch (IOException e) { try { if(os!=null) os.close(); } catch (IOException ex) { logger.error(ex.getMessage(), ex); } logger.error(e.getMessage(), e); } } /** * * @param arg0 */ private void handleCheck(InputStream is,OutputStream os,Callback callback){ StringBuilder node = new StringBuilder(""); Set<String> ins = jobManager.getJobs().keySet(); String in; java.util.Iterator<String> its = ins .iterator(); while (its.hasNext()) { in = its.next(); node.append(in); node.append(":"); node.append("success"); node.append(":"); node.append("0"); node.append("\r\n"); } try { callback.callback(HttpServletResponse.SC_OK,node.toString().getBytes("UTF-8").length); os.write(node.toString().getBytes("UTF-8")); os.flush(); os.close(); } catch (IOException e) { try { if(os!=null) os.close(); } catch (IOException ex) { logger.error(ex.getMessage(), ex); } logger.error(e.getMessage(), e); } } /** * * * sendResponseHeaders 这方法存在重复设置的问题。但一般情况下不会出现。 * * * @param arg0 * @param type * @param method * @param instance */ private void handleConfig(InputStream is,OutputStream os,Callback callback,String type,String method,String instance){ java.io.DataInputStream dis=new java.io.DataInputStream(new BufferedInputStream(is)); java.io.DataOutputStream dos=new java.io.DataOutputStream(new BufferedOutputStream(os)); FileInputStream fis=null; FileOutputStream fos=null; try{ if(instance!=null&&this.jobManager.getJobs().get(instance)!=null){ //属性文件 if("0".equals(type)){ if("get".equals(method)){ File file=new File(this.jobManager.getConfig().getConfigFile()); byte[] fd=file.getAbsolutePath().getBytes("UTF-8"); if(file.exists()){ fis=new java.io.FileInputStream(file); callback.callback(HttpServletResponse.SC_OK,4+4+fd.length+4+fis.available()); dos.writeInt(1); dos.writeInt(fd.length); dos.write(fd); dos.writeInt(fis.available()); byte[] buf=new byte[1024]; int pos=-1; while((pos=fis.read(buf))!=-1){ dos.write(buf, 0, pos); dos.flush(); } fis.close(); dos.close(); }else{ callback.callback(415,0); dos.write("property not exist".getBytes("UTF-8")); dos.flush(); dos.close(); } } if("post".equals(method)){ int flen=dis.readInt(); byte[] fb=new byte[flen]; dis.read(fb); String fName=new String(fb,"UTF-8"); boolean load=false; if(fName.endsWith(this.jobManager.getConfig().getConfigFile())) load=true; if(load){ //临时文件输出 File temp=new File(this.jobManager.getConfig().getConfigFile()+".temp"); if(temp.exists()) temp.delete(); fos=new FileOutputStream(temp); int a=-1; byte[] buf=new byte[1024]; while((a=dis.read(buf))!=-1){ fos.write(buf, 0, a); fos.flush(); } dis.close(); fos.close(); File file=new File(this.jobManager.getConfig().getConfigFile()); File bak=new File(this.jobManager.getConfig().getConfigFile()+".bak"); if(bak.exists()) bak.delete(); if(file.exists()){ file.renameTo(bak); } temp.renameTo(file); callback.callback(HttpServletResponse.SC_OK,0); dos.flush(); dos.close(); }else{ callback.callback(415,0); dos.write("file can't loaded".getBytes("UTF-8")); dos.flush(); dos.close(); } } return; } //XML文件,只能修改本地文件 if("1".equals(type)){ if(jobManager != null && jobManager.getConfig() != null){ if("get".equals(method)){ String[] configFiles=jobManager.getJobs().get(instance).getJobConfig().getReportConfigs(); FileAttr files=new FileAttr(); for (String config : configFiles) { String temp=config.substring(config.indexOf(":")+1); File cfg=new File(temp); listFile(files,cfg); } callback.callback(HttpServletResponse.SC_OK,4+8*files.size()+files.getFileLen()+files.getFnLen()); dos.writeInt(files.size()); for(int i=0;i<files.size();i++){ dos.writeInt(files.getFileName().get(i).length); dos.write(files.getFileName().get(i)); dos.writeInt(files.getFisList().get(i).available()); byte[] buf=new byte[1024]; int pos=-1; while((pos=files.getFisList().get(i).read(buf))!=-1){ dos.write(buf, 0, pos); dos.flush(); } files.getFisList().get(i).close(); } dos.close(); } if("post".equals(method)){ String[] configFiles=jobManager.getJobs().get(instance).getJobConfig().getReportConfigs(); java.util.List<String> dirs=new java.util.ArrayList<String>(); for (String config : configFiles) { String temp=config.substring(config.indexOf(":")+1); File cfg=new File(temp); if(cfg.isDirectory()){ dirs.add(cfg.getAbsolutePath()); } } int flen=dis.readInt(); byte[] fb=new byte[flen]; dis.read(fb); String fname=new String(fb,"UTF-8"); //对于上传的文件需要判断能否被分析器加载。 boolean load=false; for(String dir:dirs){ if(fname.startsWith(dir)) load=true; } if(load){ File temp=new File(fname+".temp"); if(temp.exists()) temp.delete(); fos=new FileOutputStream(temp); int a=-1; byte[] buf=new byte[1024]; while((a=dis.read(buf))!=-1){ fos.write(buf, 0, a); fos.flush(); } dis.close(); fos.close(); File file=new File(fname); File bak=new File(fname+".bak"); if(bak.exists()) bak.delete(); if(file.exists()) { file.renameTo(bak); } temp.renameTo(file); callback.callback(HttpServletResponse.SC_OK,0); dos.flush(); dos.close(); }else{ callback.callback(415,0); dos.write("file can't loaded".getBytes("UTF-8")); dos.flush(); dos.close(); } } return; }else{ callback.callback(415,0); dos.write("slave can't find xml file".getBytes("UTF-8")); dos.flush(); dos.close(); } } callback.callback(415,0); dos.write("parameter error".getBytes("UTF-8")); dos.flush(); dos.close(); }else{ callback.callback(415,0); dos.write("Can't find instance:".getBytes("UTF-8")); dos.write(instance.getBytes("UTF-8")); dos.flush(); dos.close(); } }catch(Throwable t){ try { callback.callback(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,0); dos.flush(); dos.close(); } catch (IOException e) { logger.error(e.getMessage(), e); } }finally{ try { if(fis!=null) fis.close(); if(fos!=null) fos.close(); if(dis!=null) dis.close(); if(dos!=null) dos.close(); } catch (IOException e) { logger.error(e.getMessage(), e); } } } private static class FileAttr{ private java.util.List<FileInputStream> fis=new java.util.ArrayList<FileInputStream>(); private java.util.List<byte[]> fileName=new java.util.ArrayList<byte[]>(); private int fileLen; private int fnLen; public int size(){ if(fis.size()==fileName.size()) return fis.size(); throw new java.lang.RuntimeException("the size is wrong"); } public final java.util.List<byte[]> getFileName() { return fileName; } public final void addFileName(byte[] fileName) { this.fileName.add(fileName); } public final java.util.List<FileInputStream> getFisList() { return fis; } public final void addFileInputStream(java.io.FileInputStream is) { fis.add(is); } public final void removeFileInputStream(java.io.FileInputStream is) { fis.remove(is); } public final int getFileLen() { return fileLen; } public final void addFileLen(int fileLen) { this.fileLen+=fileLen; } public final int getFnLen() { return fnLen; } public final void addFnLen(int fnLen) { this.fnLen += fnLen; } } /** * 递归枚举所有的文件 * @param files * @param file */ public static void listFile(FileAttr files,File file){ if(file.isFile()) { java.io.FileInputStream fs=null; boolean flag=false; try { fs = new java.io.FileInputStream(file); flag=true; files.addFileInputStream(fs); files.addFileLen(fs.available()); byte[] a=file.getAbsolutePath().getBytes("UTF-8"); files.addFileName(a); files.addFnLen(a.length); } catch (Exception e) { if(flag&&fs!=null) files.removeFileInputStream(fs); } return; } if(file.isDirectory()){ File[] temps = file.listFiles(new ExtFilenameFilter(".xml")); for(File f:temps){ listFile(files, f); } } } /** * 解析字符或者编码后的字符 * @param s * @param sb * @return */ private String parseName(String s, StringBuilder sb) { sb.setLength(0); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '+': sb.append(' '); break; case '%': try { sb.append((char) Integer.parseInt(s.substring(i+1, i+3), 16)); i += 2; } catch (NumberFormatException e) { throw new IllegalArgumentException(); } catch (StringIndexOutOfBoundsException e) { String rest = s.substring(i); sb.append(rest); if (rest.length()==2) i++; } break; default: sb.append(c); break; } } return sb.toString(); } }