标签:find ini java.net lan list exp isp get bst
说明:本篇强调分析对象关系,而不是类关系,主要分析为何HttpProcessor还需要依赖上篇中的连接器?为何res/对象交由链接器创建且res/req本身也依赖连接器?他们是否真的就是杂乱的纠缠在一起的?
1 package org.apache.catalina.connector.http; 2 3 import java.io.EOFException; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.InterruptedIOException; 7 import java.io.OutputStream; 8 import java.net.Socket; 9 import java.util.ArrayList; 10 import java.util.Iterator; 11 import java.util.Locale; 12 import java.util.TreeMap; 13 import javax.servlet.ServletException; 14 import javax.servlet.http.Cookie; 15 import javax.servlet.http.HttpServletRequest; 16 import javax.servlet.http.HttpServletResponse; 17 import org.apache.catalina.Lifecycle; 18 import org.apache.catalina.LifecycleException; 19 import org.apache.catalina.LifecycleListener; 20 import org.apache.catalina.Logger; 21 import org.apache.catalina.connector.http.DefaultHeaders; 22 import org.apache.catalina.connector.http.HttpConnector; 23 import org.apache.catalina.connector.http.HttpHeader; 24 import org.apache.catalina.connector.http.HttpRequestImpl; 25 import org.apache.catalina.connector.http.HttpRequestLine; 26 import org.apache.catalina.connector.http.HttpResponseImpl; 27 import org.apache.catalina.connector.http.SocketInputStream; 28 import org.apache.catalina.util.FastHttpDateFormat; 29 import org.apache.catalina.util.LifecycleSupport; 30 import org.apache.catalina.util.RequestUtil; 31 import org.apache.catalina.util.ServerInfo; 32 import org.apache.catalina.util.StringManager; 33 import org.apache.catalina.util.StringParser; 34 35 final class HttpProcessor implements Lifecycle, Runnable { 36 private static final String SERVER_INFO = ServerInfo.getServerInfo() + " (HTTP/1.1 Connector)"; 37 private boolean available = false; 38 private HttpConnector connector = null; 39 private int debug = 0; 40 private int id = 0; 41 private LifecycleSupport lifecycle = new LifecycleSupport(this); 42 private static final String match = ";jsessionid="; 43 private static final char[] SESSION_ID = ";jsessionid=".toCharArray(); 44 private StringParser parser = new StringParser(); 45 private String proxyName = null; 46 private int proxyPort = 0; 47 private HttpRequestImpl request = null; 48 private HttpResponseImpl response = null; 49 private int serverPort = 0; 50 protected StringManager sm = StringManager.getManager("org.apache.catalina.connector.http"); 51 private Socket socket = null; 52 private boolean started = false; 53 private boolean stopped = false; 54 private Thread thread = null; 55 private String threadName = null; 56 private Object threadSync = new Object(); 57 private boolean keepAlive = false; 58 private boolean http11 = true; 59 private boolean sendAck = false; 60 private static final byte[] ack = (new String("HTTP/1.1 100 Continue\r\n\r\n")).getBytes(); 61 private static final byte[] CRLF = (new String("\r\n")).getBytes(); 62 private HttpRequestLine requestLine = new HttpRequestLine(); 63 private int status = 0; 64 65 public HttpProcessor(HttpConnector connector, int id) { 66 this.connector = connector; 67 this.debug = connector.getDebug(); 68 this.id = id; 69 this.proxyName = connector.getProxyName(); 70 this.proxyPort = connector.getProxyPort(); 71 this.request = (HttpRequestImpl) connector.createRequest(); 72 this.response = (HttpResponseImpl) connector.createResponse(); 73 this.serverPort = connector.getPort(); 74 this.threadName = "HttpProcessor[" + connector.getPort() + "][" + id + "]"; 75 } 76 77 public String toString() { 78 return this.threadName; 79 } 80 81 synchronized void assign(Socket socket) { 82 while (this.available) { 83 try { 84 this.wait(); 85 } catch (InterruptedException arg2) { 86 ; 87 } 88 } 89 90 this.socket = socket; 91 this.available = true; 92 this.notifyAll(); 93 if (this.debug >= 1 && socket != null) { 94 this.log(" An incoming request is being assigned"); 95 } 96 97 } 98 99 private synchronized Socket await() { 100 while (!this.available) { 101 try { 102 this.wait(); 103 } catch (InterruptedException arg1) { 104 ; 105 } 106 } 107 108 Socket socket = this.socket; 109 this.available = false; 110 this.notifyAll(); 111 if (this.debug >= 1 && socket != null) { 112 this.log(" The incoming request has been awaited"); 113 } 114 115 return socket; 116 } 117 118 private void log(String message) { 119 Logger logger = this.connector.getContainer().getLogger(); 120 if (logger != null) { 121 logger.log(this.threadName + " " + message); 122 } 123 124 } 125 126 private void log(String message, Throwable throwable) { 127 Logger logger = this.connector.getContainer().getLogger(); 128 if (logger != null) { 129 logger.log(this.threadName + " " + message, throwable); 130 } 131 132 } 133 134 private void parseAcceptLanguage(String value) { 135 TreeMap locales = new TreeMap(); 136 int white = value.indexOf(32); 137 if (white < 0) { 138 white = value.indexOf(9); 139 } 140 141 int keys; 142 int key; 143 if (white >= 0) { 144 StringBuffer length = new StringBuffer(); 145 keys = value.length(); 146 147 for (key = 0; key < keys; ++key) { 148 char list = value.charAt(key); 149 if (list != 32 && list != 9) { 150 length.append(list); 151 } 152 } 153 154 value = length.toString(); 155 } 156 157 this.parser.setString(value); 158 int arg18 = this.parser.getLength(); 159 160 while (true) { 161 keys = this.parser.getIndex(); 162 if (keys >= arg18) { 163 Iterator arg19 = locales.keySet().iterator(); 164 165 while (arg19.hasNext()) { 166 Double arg20 = (Double) arg19.next(); 167 ArrayList arg22 = (ArrayList) locales.get(arg20); 168 169 Locale locale; 170 for (Iterator arg23 = arg22.iterator(); arg23.hasNext(); this.request.addLocale(locale)) { 171 locale = (Locale) arg23.next(); 172 if (this.debug >= 1) { 173 this.log(" Adding locale \‘" + locale + "\‘"); 174 } 175 } 176 } 177 178 return; 179 } 180 181 key = this.parser.findChar(‘,‘); 182 String arg21 = this.parser.extract(keys, key).trim(); 183 this.parser.advance(); 184 double values = 1.0D; 185 int semi = arg21.indexOf(";q="); 186 if (semi >= 0) { 187 try { 188 values = Double.parseDouble(arg21.substring(semi + 3)); 189 } catch (NumberFormatException arg17) { 190 values = 0.0D; 191 } 192 193 arg21 = arg21.substring(0, semi); 194 } 195 196 if (values >= 5.0E-5D && !"*".equals(arg21)) { 197 String language = null; 198 String country = null; 199 String variant = null; 200 int dash = arg21.indexOf(45); 201 if (dash < 0) { 202 language = arg21; 203 country = ""; 204 variant = ""; 205 } else { 206 language = arg21.substring(0, dash); 207 country = arg21.substring(dash + 1); 208 int locale1 = country.indexOf(45); 209 if (locale1 > 0) { 210 String key1 = country.substring(0, locale1); 211 variant = country.substring(locale1 + 1); 212 country = key1; 213 } else { 214 variant = ""; 215 } 216 } 217 218 Locale arg24 = new Locale(language, country, variant); 219 Double arg25 = new Double(-values); 220 ArrayList values1 = (ArrayList) locales.get(arg25); 221 if (values1 == null) { 222 values1 = new ArrayList(); 223 locales.put(arg25, values1); 224 } 225 226 values1.add(arg24); 227 } 228 } 229 } 230 231 private void parseConnection(Socket socket) throws IOException, ServletException { 232 if (this.debug >= 2) { 233 this.log(" parseConnection: address=" + socket.getInetAddress() + ", port=" + this.connector.getPort()); 234 } 235 236 this.request.setInet(socket.getInetAddress()); 237 if (this.proxyPort != 0) { 238 this.request.setServerPort(this.proxyPort); 239 } else { 240 this.request.setServerPort(this.serverPort); 241 } 242 243 this.request.setSocket(socket); 244 } 245 246 private void parseHeaders(SocketInputStream input) throws IOException, ServletException { 247 while (true) { 248 HttpHeader header = this.request.allocateHeader(); 249 input.readHeader(header); 250 if (header.nameEnd == 0) { 251 if (header.valueEnd == 0) { 252 return; 253 } 254 255 throw new ServletException(this.sm.getString("httpProcessor.parseHeaders.colon")); 256 } 257 258 String value = new String(header.value, 0, header.valueEnd); 259 if (this.debug >= 1) { 260 this.log(" Header " + new String(header.name, 0, header.nameEnd) + " = " + value); 261 } 262 263 if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) { 264 this.request.setAuthorization(value); 265 } else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) { 266 this.parseAcceptLanguage(value); 267 } else { 268 int port; 269 if (header.equals(DefaultHeaders.COOKIE_NAME)) { 270 Cookie[] n = RequestUtil.parseCookieHeader(value); 271 272 for (port = 0; port < n.length; ++port) { 273 if (n[port].getName().equals("JSESSIONID") && !this.request.isRequestedSessionIdFromCookie()) { 274 this.request.setRequestedSessionId(n[port].getValue()); 275 this.request.setRequestedSessionCookie(true); 276 this.request.setRequestedSessionURL(false); 277 if (this.debug >= 1) { 278 this.log(" Requested cookie session id is " 279 + ((HttpServletRequest) this.request.getRequest()).getRequestedSessionId()); 280 } 281 } 282 283 if (this.debug >= 1) { 284 this.log(" Adding cookie " + n[port].getName() + "=" + n[port].getValue()); 285 } 286 287 this.request.addCookie(n[port]); 288 } 289 } else { 290 int arg9; 291 if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) { 292 boolean arg8 = true; 293 294 try { 295 arg9 = Integer.parseInt(value); 296 } catch (Exception arg7) { 297 throw new ServletException(this.sm.getString("httpProcessor.parseHeaders.contentLength")); 298 } 299 300 this.request.setContentLength(arg9); 301 } else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) { 302 this.request.setContentType(value); 303 } else if (header.equals(DefaultHeaders.HOST_NAME)) { 304 arg9 = value.indexOf(58); 305 if (arg9 < 0) { 306 if (this.connector.getScheme().equals("http")) { 307 this.request.setServerPort(80); 308 } else if (this.connector.getScheme().equals("https")) { 309 this.request.setServerPort(443); 310 } 311 312 if (this.proxyName != null) { 313 this.request.setServerName(this.proxyName); 314 } else { 315 this.request.setServerName(value); 316 } 317 } else { 318 if (this.proxyName != null) { 319 this.request.setServerName(this.proxyName); 320 } else { 321 this.request.setServerName(value.substring(0, arg9).trim()); 322 } 323 324 if (this.proxyPort != 0) { 325 this.request.setServerPort(this.proxyPort); 326 } else { 327 boolean arg10 = true; 328 329 try { 330 port = Integer.parseInt(value.substring(arg9 + 1).trim()); 331 } catch (Exception arg6) { 332 throw new ServletException( 333 this.sm.getString("httpProcessor.parseHeaders.portNumber")); 334 } 335 336 this.request.setServerPort(port); 337 } 338 } 339 } else if (header.equals(DefaultHeaders.CONNECTION_NAME)) { 340 if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) { 341 this.keepAlive = false; 342 this.response.setHeader("Connection", "close"); 343 } 344 } else if (header.equals(DefaultHeaders.EXPECT_NAME)) { 345 if (!header.valueEquals(DefaultHeaders.EXPECT_100_VALUE)) { 346 throw new ServletException( 347 this.sm.getString("httpProcessor.parseHeaders.unknownExpectation")); 348 } 349 350 this.sendAck = true; 351 } else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) { 352 ; 353 } 354 } 355 } 356 357 this.request.nextHeader(); 358 } 359 } 360 361 private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException { 362 input.readRequestLine(this.requestLine); 363 this.status = 1; 364 String method = new String(this.requestLine.method, 0, this.requestLine.methodEnd); 365 String uri = null; 366 String protocol = new String(this.requestLine.protocol, 0, this.requestLine.protocolEnd); 367 if (protocol.length() == 0) { 368 protocol = "HTTP/0.9"; 369 } 370 371 if (protocol.equals("HTTP/1.1")) { 372 this.http11 = true; 373 this.sendAck = false; 374 } else { 375 this.http11 = false; 376 this.sendAck = false; 377 this.keepAlive = false; 378 } 379 380 if (method.length() < 1) { 381 throw new ServletException(this.sm.getString("httpProcessor.parseRequest.method")); 382 } else if (this.requestLine.uriEnd < 1) { 383 throw new ServletException(this.sm.getString("httpProcessor.parseRequest.uri")); 384 } else { 385 int question = this.requestLine.indexOf("?"); 386 if (question >= 0) { 387 this.request.setQueryString( 388 new String(this.requestLine.uri, question + 1, this.requestLine.uriEnd - question - 1)); 389 if (this.debug >= 1) { 390 this.log(" Query string is " + ((HttpServletRequest) this.request.getRequest()).getQueryString()); 391 } 392 393 uri = new String(this.requestLine.uri, 0, question); 394 } else { 395 this.request.setQueryString((String) null); 396 uri = new String(this.requestLine.uri, 0, this.requestLine.uriEnd); 397 } 398 399 int semicolon; 400 if (!uri.startsWith("/")) { 401 semicolon = uri.indexOf("://"); 402 if (semicolon != -1) { 403 semicolon = uri.indexOf(47, semicolon + 3); 404 if (semicolon == -1) { 405 uri = ""; 406 } else { 407 uri = uri.substring(semicolon); 408 } 409 } 410 } 411 412 semicolon = uri.indexOf(";jsessionid="); 413 String normalizedUri; 414 if (semicolon >= 0) { 415 normalizedUri = uri.substring(semicolon + ";jsessionid=".length()); 416 int semicolon2 = normalizedUri.indexOf(59); 417 if (semicolon2 >= 0) { 418 this.request.setRequestedSessionId(normalizedUri.substring(0, semicolon2)); 419 normalizedUri = normalizedUri.substring(semicolon2); 420 } else { 421 this.request.setRequestedSessionId(normalizedUri); 422 normalizedUri = ""; 423 } 424 425 this.request.setRequestedSessionURL(true); 426 uri = uri.substring(0, semicolon) + normalizedUri; 427 if (this.debug >= 1) { 428 this.log(" Requested URL session id is " 429 + ((HttpServletRequest) this.request.getRequest()).getRequestedSessionId()); 430 } 431 } else { 432 this.request.setRequestedSessionId((String) null); 433 this.request.setRequestedSessionURL(false); 434 } 435 436 normalizedUri = this.normalize(uri); 437 if (this.debug >= 1) { 438 this.log("Normalized: \‘" + uri + "\‘ to \‘" + normalizedUri + "\‘"); 439 } 440 441 this.request.setMethod(method); 442 this.request.setProtocol(protocol); 443 if (normalizedUri != null) { 444 this.request.setRequestURI(normalizedUri); 445 } else { 446 this.request.setRequestURI(uri); 447 } 448 449 this.request.setSecure(this.connector.getSecure()); 450 this.request.setScheme(this.connector.getScheme()); 451 if (normalizedUri == null) { 452 this.log(" Invalid request URI: \‘" + uri + "\‘"); 453 throw new ServletException("Invalid URI: " + uri + "\‘"); 454 } else { 455 if (this.debug >= 1) { 456 this.log(" Request is \‘" + method + "\‘ for \‘" + uri + "\‘ with protocol \‘" + protocol + "\‘"); 457 } 458 459 } 460 } 461 } 462 463 protected String normalize(String path) { 464 if (path == null) { 465 return null; 466 } else { 467 String normalized = path; 468 if (path.startsWith("/%7E") || path.startsWith("/%7e")) { 469 normalized = "/~" + path.substring(4); 470 } 471 472 if (normalized.indexOf("%25") < 0 && normalized.indexOf("%2F") < 0 && normalized.indexOf("%2E") < 0 473 && normalized.indexOf("%5C") < 0 && normalized.indexOf("%2f") < 0 && normalized.indexOf("%2e") < 0 474 && normalized.indexOf("%5c") < 0) { 475 if (normalized.equals("/.")) { 476 return "/"; 477 } else { 478 if (normalized.indexOf(92) >= 0) { 479 normalized = normalized.replace(‘\\‘, ‘/‘); 480 } 481 482 if (!normalized.startsWith("/")) { 483 normalized = "/" + normalized; 484 } 485 486 while (true) { 487 int index = normalized.indexOf("//"); 488 if (index < 0) { 489 while (true) { 490 index = normalized.indexOf("/./"); 491 if (index < 0) { 492 while (true) { 493 index = normalized.indexOf("/../"); 494 if (index < 0) { 495 return normalized.indexOf("/...") >= 0 ? null : normalized; 496 } 497 498 if (index == 0) { 499 return null; 500 } 501 502 int index2 = normalized.lastIndexOf(47, index - 1); 503 normalized = normalized.substring(0, index2) + normalized.substring(index + 3); 504 } 505 } 506 507 normalized = normalized.substring(0, index) + normalized.substring(index + 2); 508 } 509 } 510 511 normalized = normalized.substring(0, index) + normalized.substring(index + 1); 512 } 513 } 514 } else { 515 return null; 516 } 517 } 518 } 519 520 private void ackRequest(OutputStream output) throws IOException { 521 if (this.sendAck) { 522 output.write(ack); 523 } 524 525 } 526 527 private void process(Socket socket) { 528 boolean ok = true; 529 boolean finishResponse = true; 530 SocketInputStream input = null; 531 OutputStream output = null; 532 533 try { 534 input = new SocketInputStream(socket.getInputStream(), this.connector.getBufferSize()); 535 } catch (Exception arg26) { 536 this.log("process.create", arg26); 537 ok = false; 538 } 539 540 this.keepAlive = true; 541 542 while (!this.stopped && ok && this.keepAlive) { 543 finishResponse = true; 544 545 try { 546 this.request.setStream(input); 547 this.request.setResponse(this.response); 548 output = socket.getOutputStream(); 549 this.response.setStream(output); 550 this.response.setRequest(this.request); 551 ((HttpServletResponse) this.response.getResponse()).setHeader("Server", SERVER_INFO); 552 } catch (Exception arg25) { 553 this.log("process.create", arg25); 554 ok = false; 555 } 556 557 try { 558 if (ok) { 559 this.parseConnection(socket); 560 this.parseRequest(input, output); 561 if (!this.request.getRequest().getProtocol().startsWith("HTTP/0")) { 562 this.parseHeaders(input); 563 } 564 565 if (this.http11) { 566 this.ackRequest(output); 567 if (this.connector.isChunkingAllowed()) { 568 this.response.setAllowChunking(true); 569 } 570 } 571 } 572 } catch (EOFException arg27) { 573 ok = false; 574 finishResponse = false; 575 } catch (ServletException arg28) { 576 ok = false; 577 578 try { 579 ((HttpServletResponse) this.response.getResponse()).sendError(400); 580 } catch (Exception arg24) { 581 ; 582 } 583 } catch (InterruptedIOException arg29) { 584 InterruptedIOException e = arg29; 585 if (this.debug > 1) { 586 try { 587 this.log("process.parse", e); 588 ((HttpServletResponse) this.response.getResponse()).sendError(400); 589 } catch (Exception arg23) { 590 ; 591 } 592 } 593 594 ok = false; 595 } catch (Exception arg30) { 596 Exception f = arg30; 597 598 try { 599 this.log("process.parse", f); 600 ((HttpServletResponse) this.response.getResponse()).sendError(400); 601 } catch (Exception arg22) { 602 ; 603 } 604 605 ok = false; 606 } 607 608 try { 609 this.response.setHeader("Date", FastHttpDateFormat.getCurrentDate()); 610 if (ok) { 611 this.connector.getContainer().invoke(this.request, this.response); 612 } 613 } catch (ServletException arg19) { 614 this.log("process.invoke", arg19); 615 616 try { 617 ((HttpServletResponse) this.response.getResponse()).sendError(500); 618 } catch (Exception arg18) { 619 ; 620 } 621 622 ok = false; 623 } catch (InterruptedIOException arg20) { 624 ok = false; 625 } catch (Throwable arg21) { 626 this.log("process.invoke", arg21); 627 628 try { 629 ((HttpServletResponse) this.response.getResponse()).sendError(500); 630 } catch (Exception arg17) { 631 ; 632 } 633 634 ok = false; 635 } 636 637 if (finishResponse) { 638 try { 639 this.response.finishResponse(); 640 } catch (IOException arg15) { 641 ok = false; 642 } catch (Throwable arg16) { 643 this.log("process.invoke", arg16); 644 ok = false; 645 } 646 647 try { 648 this.request.finishRequest(); 649 } catch (IOException arg13) { 650 ok = false; 651 } catch (Throwable arg14) { 652 this.log("process.invoke", arg14); 653 ok = false; 654 } 655 656 try { 657 if (output != null) { 658 output.flush(); 659 } 660 } catch (IOException arg12) { 661 ok = false; 662 } 663 } 664 665 if ("close".equals(this.response.getHeader("Connection"))) { 666 this.keepAlive = false; 667 } 668 669 this.status = 0; 670 this.request.recycle(); 671 this.response.recycle(); 672 } 673 674 try { 675 this.shutdownInput(input); 676 socket.close(); 677 } catch (IOException arg10) { 678 ; 679 } catch (Throwable arg11) { 680 this.log("process.invoke", arg11); 681 } 682 683 socket = null; 684 } 685 686 protected void shutdownInput(InputStream input) { 687 try { 688 int e = input.available(); 689 if (e > 0) { 690 input.skip((long) e); 691 } 692 } catch (Throwable arg2) { 693 ; 694 } 695 696 } 697 698 public void run() { 699 while (!this.stopped) { 700 Socket socket = this.await(); 701 if (socket != null) { 702 try { 703 this.process(socket); 704 } catch (Throwable arg4) { 705 this.log("process.invoke", arg4); 706 } 707 708 this.connector.recycle(this); 709 } 710 } 711 712 Object socket1 = this.threadSync; 713 synchronized (socket1) { 714 this.threadSync.notifyAll(); 715 } 716 } 717 718 private void threadStart() { 719 this.log(this.sm.getString("httpProcessor.starting")); 720 this.thread = new Thread(this, this.threadName); 721 this.thread.setDaemon(true); 722 this.thread.start(); 723 if (this.debug >= 1) { 724 this.log(" Background thread has been started"); 725 } 726 727 } 728 729 private void threadStop() { 730 this.log(this.sm.getString("httpProcessor.stopping")); 731 this.stopped = true; 732 this.assign((Socket) null); 733 if (this.status != 0) { 734 Object arg0 = this.threadSync; 735 synchronized (arg0) { 736 try { 737 this.threadSync.wait(5000L); 738 } catch (InterruptedException arg3) { 739 ; 740 } 741 } 742 } 743 744 this.thread = null; 745 } 746 747 public void addLifecycleListener(LifecycleListener listener) { 748 this.lifecycle.addLifecycleListener(listener); 749 } 750 751 public LifecycleListener[] findLifecycleListeners() { 752 return this.lifecycle.findLifecycleListeners(); 753 } 754 755 public void removeLifecycleListener(LifecycleListener listener) { 756 this.lifecycle.removeLifecycleListener(listener); 757 } 758 759 public void start() throws LifecycleException { 760 if (this.started) { 761 throw new LifecycleException(this.sm.getString("httpProcessor.alreadyStarted")); 762 } else { 763 this.lifecycle.fireLifecycleEvent("start", (Object) null); 764 this.started = true; 765 this.threadStart(); 766 } 767 } 768 769 public void stop() throws LifecycleException { 770 if (!this.started) { 771 throw new LifecycleException(this.sm.getString("httpProcessor.notStarted")); 772 } else { 773 this.lifecycle.fireLifecycleEvent("stop", (Object) null); 774 this.started = false; 775 this.threadStop(); 776 } 777 } 778 }
分析:程序入口是上一篇笔记中的HttpProcessor的start方法和assign()方法,
基于之前案例的感性认识,Tomcat连接器源码解读2---连接器与其中HttpProcessor、response、request等对象的关系
标签:find ini java.net lan list exp isp get bst
原文地址:https://www.cnblogs.com/10000miles/p/9230568.html