(this.running)) {
326 | clientHandler.close();
327 | }
328 | }
329 |
330 | @Override
331 | public void closed(ClientHandler clientHandler) {
332 | this.running.remove(clientHandler);
333 | }
334 |
335 | @Override
336 | public void exec(ClientHandler clientHandler) {
337 | ++this.requestCount;
338 | Thread t = new Thread(clientHandler);
339 | t.setDaemon(true);
340 | t.setName("NanoHttpd Request Processor (#" + this.requestCount + ")");
341 | this.running.add(clientHandler);
342 | t.start();
343 | }
344 | }
345 |
346 | /**
347 | * Default strategy for creating and cleaning up temporary files.
348 |
349 | *
350 | * By default, files are created by File.createTempFile()
in
351 | * the directory specified.
352 | *
353 | */
354 | public static class DefaultTempFile implements TempFile {
355 |
356 | private final File file;
357 |
358 | private final OutputStream fstream;
359 |
360 | public DefaultTempFile(String tempdir) throws IOException {
361 | this.file = File.createTempFile("NanoHTTPD-", "", new File(tempdir));
362 | this.fstream = new FileOutputStream(this.file);
363 | }
364 |
365 | @Override
366 | public void delete() throws Exception {
367 | safeClose(this.fstream);
368 | if (!this.file.delete()) {
369 | throw new Exception("could not delete temporary file");
370 | }
371 | }
372 |
373 | @Override
374 | public String getName() {
375 | return this.file.getAbsolutePath();
376 | }
377 |
378 | @Override
379 | public OutputStream open() throws Exception {
380 | return this.fstream;
381 | }
382 | }
383 |
384 | /**
385 | * Default strategy for creating and cleaning up temporary files.
386 |
387 | *
388 | * This class stores its files in the standard location (that is, wherever
389 | * java.io.tmpdir
points to). Files are added to an internal
390 | * list, and deleted when no longer needed (that is, when
391 | * clear()
is invoked at the end of processing a request).
392 | *
393 | */
394 | public static class DefaultTempFileManager implements TempFileManager {
395 |
396 | private final String tmpdir;
397 |
398 | private final List tempFiles;
399 |
400 | public DefaultTempFileManager() {
401 | this.tmpdir = System.getProperty("java.io.tmpdir");
402 | this.tempFiles = new ArrayList();
403 | }
404 |
405 | @Override
406 | public void clear() {
407 | for (TempFile file : this.tempFiles) {
408 | try {
409 | file.delete();
410 | } catch (Exception ignored) {
411 | NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored);
412 | }
413 | }
414 | this.tempFiles.clear();
415 | }
416 |
417 | @Override
418 | public TempFile createTempFile() throws Exception {
419 | DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);
420 | this.tempFiles.add(tempFile);
421 | return tempFile;
422 | }
423 | }
424 |
425 | /**
426 | * Default strategy for creating and cleaning up temporary files.
427 | */
428 | private class DefaultTempFileManagerFactory implements TempFileManagerFactory {
429 |
430 | @Override
431 | public TempFileManager create() {
432 | return new DefaultTempFileManager();
433 | }
434 | }
435 |
436 | private static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)";
437 |
438 | private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE);
439 |
440 | private static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)";
441 |
442 | private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE);
443 |
444 | private static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]";
445 |
446 | private static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX);
447 |
448 | protected class HTTPSession implements IHTTPSession {
449 |
450 | public static final int BUFSIZE = 8192;
451 |
452 | private final TempFileManager tempFileManager;
453 |
454 | private final OutputStream outputStream;
455 |
456 | private final PushbackInputStream inputStream;
457 |
458 | private int splitbyte;
459 |
460 | private int rlen;
461 |
462 | private String uri;
463 |
464 | private Method method;
465 |
466 | private Map parms;
467 |
468 | private Map headers;
469 |
470 | private CookieHandler cookies;
471 |
472 | private String queryParameterString;
473 |
474 | private String remoteIp;
475 |
476 | private String protocolVersion;
477 |
478 | public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
479 | this.tempFileManager = tempFileManager;
480 | this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);
481 | this.outputStream = outputStream;
482 | }
483 |
484 | public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
485 | this.tempFileManager = tempFileManager;
486 | this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);
487 | this.outputStream = outputStream;
488 | this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString();
489 | this.headers = new HashMap();
490 | }
491 |
492 | /**
493 | * Decodes the sent headers and loads the data into Key/value pairs
494 | */
495 | private void decodeHeader(BufferedReader in, Map pre, Map parms, Map headers) throws ResponseException {
496 | try {
497 | // Read the request line
498 | String inLine = in.readLine();
499 | if (inLine == null) {
500 | return;
501 | }
502 |
503 | StringTokenizer st = new StringTokenizer(inLine);
504 | if (!st.hasMoreTokens()) {
505 | throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
506 | }
507 |
508 | pre.put("method", st.nextToken());
509 |
510 | if (!st.hasMoreTokens()) {
511 | throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
512 | }
513 |
514 | String uri = st.nextToken();
515 |
516 | // Decode parameters from the URI
517 | int qmi = uri.indexOf('?');
518 | if (qmi >= 0) {
519 | decodeParms(uri.substring(qmi + 1), parms);
520 | uri = decodePercent(uri.substring(0, qmi));
521 | } else {
522 | uri = decodePercent(uri);
523 | }
524 |
525 | // If there's another token, its protocol version,
526 | // followed by HTTP headers.
527 | // NOTE: this now forces header names lower case since they are
528 | // case insensitive and vary by client.
529 | if (st.hasMoreTokens()) {
530 | protocolVersion = st.nextToken();
531 | } else {
532 | protocolVersion = "HTTP/1.1";
533 | NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange. Assuming HTTP/1.1.");
534 | }
535 | String line = in.readLine();
536 | while (line != null && line.trim().length() > 0) {
537 | int p = line.indexOf(':');
538 | if (p >= 0) {
539 | headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());
540 | }
541 | line = in.readLine();
542 | }
543 |
544 | pre.put("uri", uri);
545 | } catch (IOException ioe) {
546 | throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
547 | }
548 | }
549 |
550 | /**
551 | * Decodes the Multipart Body data and put it into Key/Value pairs.
552 | */
553 | private void decodeMultipartFormData(String boundary, ByteBuffer fbuf, Map parms, Map files) throws ResponseException {
554 | try {
555 | int[] boundary_idxs = getBoundaryPositions(fbuf, boundary.getBytes());
556 | if (boundary_idxs.length < 2) {
557 | throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings.");
558 | }
559 |
560 | final int MAX_HEADER_SIZE = 1024;
561 | byte[] part_header_buff = new byte[MAX_HEADER_SIZE];
562 | for (int bi = 0; bi < boundary_idxs.length - 1; bi++) {
563 | fbuf.position(boundary_idxs[bi]);
564 | int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE;
565 | fbuf.get(part_header_buff, 0, len);
566 | ByteArrayInputStream bais = new ByteArrayInputStream(part_header_buff, 0, len);
567 | BufferedReader in = new BufferedReader(new InputStreamReader(bais, Charset.forName("US-ASCII")));
568 |
569 | // First line is boundary string
570 | String mpline = in.readLine();
571 | if (!mpline.contains(boundary)) {
572 | throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary.");
573 | }
574 |
575 | String part_name = null, file_name = null, content_type = null;
576 | // Parse the reset of the header lines
577 | mpline = in.readLine();
578 | while (mpline != null && mpline.trim().length() > 0) {
579 | Matcher matcher = CONTENT_DISPOSITION_PATTERN.matcher(mpline);
580 | if (matcher.matches()) {
581 | String attributeString = matcher.group(2);
582 | matcher = CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString);
583 | while (matcher.find()) {
584 | String key = matcher.group(1);
585 | if (key.equalsIgnoreCase("name")) {
586 | part_name = matcher.group(2);
587 | } else if (key.equalsIgnoreCase("filename")) {
588 | file_name = matcher.group(2);
589 | }
590 | }
591 | }
592 | matcher = CONTENT_TYPE_PATTERN.matcher(mpline);
593 | if (matcher.matches()) {
594 | content_type = matcher.group(2).trim();
595 | }
596 | mpline = in.readLine();
597 | }
598 |
599 | // Read the part data
600 | int part_header_len = len - (int) in.skip(MAX_HEADER_SIZE);
601 | if (part_header_len >= len - 4) {
602 | throw new ResponseException(Response.Status.INTERNAL_ERROR, "Multipart header size exceeds MAX_HEADER_SIZE.");
603 | }
604 | int part_data_start = boundary_idxs[bi] + part_header_len;
605 | int part_data_end = boundary_idxs[bi + 1] - 4;
606 |
607 | fbuf.position(part_data_start);
608 | if (content_type == null) {
609 | // Read the part into a string
610 | byte[] data_bytes = new byte[part_data_end - part_data_start];
611 | fbuf.get(data_bytes);
612 | parms.put(part_name, new String(data_bytes));
613 | } else {
614 | // Read it into a file
615 | String path = saveTmpFile(fbuf, part_data_start, part_data_end - part_data_start);
616 | if (!files.containsKey(part_name)) {
617 | files.put(part_name, path);
618 | } else {
619 | int count = 2;
620 | while (files.containsKey(part_name + count)) {
621 | count++;
622 | }
623 | files.put(part_name + count, path);
624 | }
625 | parms.put(part_name, file_name);
626 | }
627 | }
628 | } catch (ResponseException re) {
629 | throw re;
630 | } catch (Exception e) {
631 | throw new ResponseException(Response.Status.INTERNAL_ERROR, e.toString());
632 | }
633 | }
634 |
635 | /**
636 | * Decodes parameters in percent-encoded URI-format ( e.g.
637 | * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given
638 | * Map. NOTE: this doesn't support multiple identical keys due to the
639 | * simplicity of Map.
640 | */
641 | private void decodeParms(String parms, Map p) {
642 | if (parms == null) {
643 | this.queryParameterString = "";
644 | return;
645 | }
646 |
647 | this.queryParameterString = parms;
648 | StringTokenizer st = new StringTokenizer(parms, "&");
649 | while (st.hasMoreTokens()) {
650 | String e = st.nextToken();
651 | int sep = e.indexOf('=');
652 | if (sep >= 0) {
653 | p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1)));
654 | } else {
655 | p.put(decodePercent(e).trim(), "");
656 | }
657 | }
658 | }
659 |
660 | @Override
661 | public void execute() throws IOException {
662 | Response r = null;
663 | try {
664 | // Read the first 8192 bytes.
665 | // The full header should fit in here.
666 | // Apache's default header limit is 8KB.
667 | // Do NOT assume that a single read will get the entire header
668 | // at once!
669 | byte[] buf = new byte[HTTPSession.BUFSIZE];
670 | this.splitbyte = 0;
671 | this.rlen = 0;
672 |
673 | int read = -1;
674 | try {
675 | read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);
676 | } catch (Exception e) {
677 | safeClose(this.inputStream);
678 | safeClose(this.outputStream);
679 | throw new SocketException("NanoHttpd Shutdown");
680 | }
681 | if (read == -1) {
682 | // socket was been closed
683 | safeClose(this.inputStream);
684 | safeClose(this.outputStream);
685 | throw new SocketException("NanoHttpd Shutdown");
686 | }
687 | while (read > 0) {
688 | this.rlen += read;
689 | this.splitbyte = findHeaderEnd(buf, this.rlen);
690 | if (this.splitbyte > 0) {
691 | break;
692 | }
693 | read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);
694 | }
695 |
696 | if (this.splitbyte < this.rlen) {
697 | this.inputStream.unread(buf, this.splitbyte, this.rlen - this.splitbyte);
698 | }
699 |
700 | this.parms = new HashMap();
701 | if (null == this.headers) {
702 | this.headers = new HashMap();
703 | } else {
704 | this.headers.clear();
705 | }
706 |
707 | if (null != this.remoteIp) {
708 | this.headers.put("remote-addr", this.remoteIp);
709 | this.headers.put("http-client-ip", this.remoteIp);
710 | }
711 |
712 | // Create a BufferedReader for parsing the header.
713 | BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen)));
714 |
715 | // Decode the header into parms and header java properties
716 | Map pre = new HashMap();
717 | decodeHeader(hin, pre, this.parms, this.headers);
718 |
719 | this.method = Method.lookup(pre.get("method"));
720 | if (this.method == null) {
721 | throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
722 | }
723 |
724 | this.uri = pre.get("uri");
725 |
726 | this.cookies = new CookieHandler(this.headers);
727 |
728 | String connection = this.headers.get("connection");
729 | boolean keepAlive = protocolVersion.equals("HTTP/1.1") && (connection == null || !connection.matches("(?i).*close.*"));
730 |
731 | // Ok, now do the serve()
732 | r = serve(this);
733 | if (r == null) {
734 | throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
735 | } else {
736 | String acceptEncoding = this.headers.get("accept-encoding");
737 | this.cookies.unloadQueue(r);
738 | r.setRequestMethod(this.method);
739 | r.setGzipEncoding(useGzipWhenAccepted(r) && acceptEncoding != null && acceptEncoding.contains("gzip"));
740 | r.setKeepAlive(keepAlive);
741 | r.send(this.outputStream);
742 | }
743 | if (!keepAlive || "close".equalsIgnoreCase(r.getHeader("connection"))) {
744 | throw new SocketException("NanoHttpd Shutdown");
745 | }
746 | } catch (SocketException e) {
747 | // throw it out to close socket object (finalAccept)
748 | throw e;
749 | } catch (SocketTimeoutException ste) {
750 | // treat socket timeouts the same way we treat socket exceptions
751 | // i.e. close the stream & finalAccept object by throwing the
752 | // exception up the call stack.
753 | throw ste;
754 | } catch (IOException ioe) {
755 | Response resp = newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
756 | resp.send(this.outputStream);
757 | safeClose(this.outputStream);
758 | } catch (ResponseException re) {
759 | Response resp = newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
760 | resp.send(this.outputStream);
761 | safeClose(this.outputStream);
762 | } finally {
763 | safeClose(r);
764 | this.tempFileManager.clear();
765 | }
766 | }
767 |
768 | /**
769 | * Find byte index separating header from body. It must be the last byte
770 | * of the first two sequential new lines.
771 | */
772 | private int findHeaderEnd(final byte[] buf, int rlen) {
773 | int splitbyte = 0;
774 | while (splitbyte + 3 < rlen) {
775 | if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
776 | return splitbyte + 4;
777 | }
778 | splitbyte++;
779 | }
780 | return 0;
781 | }
782 |
783 | /**
784 | * Find the byte positions where multipart boundaries start. This reads
785 | * a large block at a time and uses a temporary buffer to optimize
786 | * (memory mapped) file access.
787 | */
788 | private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {
789 | int[] res = new int[0];
790 | if (b.remaining() < boundary.length) {
791 | return res;
792 | }
793 |
794 | int search_window_pos = 0;
795 | byte[] search_window = new byte[4 * 1024 + boundary.length];
796 |
797 | int first_fill = (b.remaining() < search_window.length) ? b.remaining() : search_window.length;
798 | b.get(search_window, 0, first_fill);
799 | int new_bytes = first_fill - boundary.length;
800 |
801 | do {
802 | // Search the search_window
803 | for (int j = 0; j < new_bytes; j++) {
804 | for (int i = 0; i < boundary.length; i++) {
805 | if (search_window[j + i] != boundary[i])
806 | break;
807 | if (i == boundary.length - 1) {
808 | // Match found, add it to results
809 | int[] new_res = new int[res.length + 1];
810 | System.arraycopy(res, 0, new_res, 0, res.length);
811 | new_res[res.length] = search_window_pos + j;
812 | res = new_res;
813 | }
814 | }
815 | }
816 | search_window_pos += new_bytes;
817 |
818 | // Copy the end of the buffer to the start
819 | System.arraycopy(search_window, search_window.length - boundary.length, search_window, 0, boundary.length);
820 |
821 | // Refill search_window
822 | new_bytes = search_window.length - boundary.length;
823 | new_bytes = (b.remaining() < new_bytes) ? b.remaining() : new_bytes;
824 | b.get(search_window, boundary.length, new_bytes);
825 | } while (new_bytes > 0);
826 | return res;
827 | }
828 |
829 | @Override
830 | public CookieHandler getCookies() {
831 | return this.cookies;
832 | }
833 |
834 | @Override
835 | public final Map getHeaders() {
836 | return this.headers;
837 | }
838 |
839 | @Override
840 | public final InputStream getInputStream() {
841 | return this.inputStream;
842 | }
843 |
844 | @Override
845 | public final Method getMethod() {
846 | return this.method;
847 | }
848 |
849 | @Override
850 | public final Map getParms() {
851 | return this.parms;
852 | }
853 |
854 | @Override
855 | public String getQueryParameterString() {
856 | return this.queryParameterString;
857 | }
858 |
859 | private RandomAccessFile getTmpBucket() {
860 | try {
861 | TempFile tempFile = this.tempFileManager.createTempFile();
862 | return new RandomAccessFile(tempFile.getName(), "rw");
863 | } catch (Exception e) {
864 | throw new Error(e); // we won't recover, so throw an error
865 | }
866 | }
867 |
868 | @Override
869 | public final String getUri() {
870 | return this.uri;
871 | }
872 |
873 | @Override
874 | public void parseBody(Map files) throws IOException, ResponseException {
875 | final int REQUEST_BUFFER_LEN = 512;
876 | final int MEMORY_STORE_LIMIT = 1024;
877 | RandomAccessFile randomAccessFile = null;
878 | try {
879 | long size;
880 | if (this.headers.containsKey("content-length")) {
881 | size = Integer.parseInt(this.headers.get("content-length"));
882 | } else if (this.splitbyte < this.rlen) {
883 | size = this.rlen - this.splitbyte;
884 | } else {
885 | size = 0;
886 | }
887 |
888 | ByteArrayOutputStream baos = null;
889 | DataOutput request_data_output = null;
890 |
891 | // Store the request in memory or a file, depending on size
892 | if (size < MEMORY_STORE_LIMIT) {
893 | baos = new ByteArrayOutputStream();
894 | request_data_output = new DataOutputStream(baos);
895 | } else {
896 | randomAccessFile = getTmpBucket();
897 | request_data_output = randomAccessFile;
898 | }
899 |
900 | // Read all the body and write it to request_data_output
901 | byte[] buf = new byte[REQUEST_BUFFER_LEN];
902 | while (this.rlen >= 0 && size > 0) {
903 | this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, REQUEST_BUFFER_LEN));
904 | size -= this.rlen;
905 | if (this.rlen > 0) {
906 | request_data_output.write(buf, 0, this.rlen);
907 | }
908 | }
909 |
910 | ByteBuffer fbuf = null;
911 | if (baos != null) {
912 | fbuf = ByteBuffer.wrap(baos.toByteArray(), 0, baos.size());
913 | } else {
914 | fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length());
915 | randomAccessFile.seek(0);
916 | }
917 |
918 | // If the method is POST, there may be parameters
919 | // in data section, too, read it:
920 | if (Method.POST.equals(this.method)) {
921 | String contentType = "";
922 | String contentTypeHeader = this.headers.get("content-type");
923 |
924 | StringTokenizer st = null;
925 | if (contentTypeHeader != null) {
926 | st = new StringTokenizer(contentTypeHeader, ",; ");
927 | if (st.hasMoreTokens()) {
928 | contentType = st.nextToken();
929 | }
930 | }
931 |
932 | if ("multipart/form-data".equalsIgnoreCase(contentType)) {
933 | // Handle multipart/form-data
934 | if (!st.hasMoreTokens()) {
935 | throw new ResponseException(Response.Status.BAD_REQUEST,
936 | "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
937 | }
938 |
939 | String boundaryStartString = "boundary=";
940 | int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length();
941 | String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());
942 | if (boundary.startsWith("\"") && boundary.endsWith("\"")) {
943 | boundary = boundary.substring(1, boundary.length() - 1);
944 | }
945 |
946 | decodeMultipartFormData(boundary, fbuf, this.parms, files);
947 | } else {
948 | byte[] postBytes = new byte[fbuf.remaining()];
949 | fbuf.get(postBytes);
950 | String postLine = new String(postBytes).trim();
951 | // Handle application/x-www-form-urlencoded
952 | if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
953 | decodeParms(postLine, this.parms);
954 | } else if (postLine.length() != 0) {
955 | // Special case for raw POST data => create a
956 | // special files entry "postData" with raw content
957 | // data
958 | files.put("postData", postLine);
959 | }
960 | }
961 | } else if (Method.PUT.equals(this.method)) {
962 | files.put("content", saveTmpFile(fbuf, 0, fbuf.limit()));
963 | }
964 | } finally {
965 | safeClose(randomAccessFile);
966 | }
967 | }
968 |
969 | /**
970 | * Retrieves the content of a sent file and saves it to a temporary
971 | * file. The full path to the saved file is returned.
972 | */
973 | private String saveTmpFile(ByteBuffer b, int offset, int len) {
974 | String path = "";
975 | if (len > 0) {
976 | FileOutputStream fileOutputStream = null;
977 | try {
978 | TempFile tempFile = this.tempFileManager.createTempFile();
979 | ByteBuffer src = b.duplicate();
980 | fileOutputStream = new FileOutputStream(tempFile.getName());
981 | FileChannel dest = fileOutputStream.getChannel();
982 | src.position(offset).limit(offset + len);
983 | dest.write(src.slice());
984 | path = tempFile.getName();
985 | } catch (Exception e) { // Catch exception if any
986 | throw new Error(e); // we won't recover, so throw an error
987 | } finally {
988 | safeClose(fileOutputStream);
989 | }
990 | }
991 | return path;
992 | }
993 | }
994 |
995 | /**
996 | * Handles one session, i.e. parses the HTTP request and returns the
997 | * response.
998 | */
999 | public interface IHTTPSession {
1000 |
1001 | void execute() throws IOException;
1002 |
1003 | CookieHandler getCookies();
1004 |
1005 | Map getHeaders();
1006 |
1007 | InputStream getInputStream();
1008 |
1009 | Method getMethod();
1010 |
1011 | Map getParms();
1012 |
1013 | String getQueryParameterString();
1014 |
1015 | /**
1016 | * @return the path part of the URL.
1017 | */
1018 | String getUri();
1019 |
1020 | /**
1021 | * Adds the files in the request body to the files map.
1022 | *
1023 | * @param files
1024 | * map to modify
1025 | */
1026 | void parseBody(Map files) throws IOException, ResponseException;
1027 | }
1028 |
1029 | /**
1030 | * HTTP Request methods, with the ability to decode a String
1031 | * back to its enum value.
1032 | */
1033 | public enum Method {
1034 | GET,
1035 | PUT,
1036 | POST,
1037 | DELETE,
1038 | HEAD,
1039 | OPTIONS;
1040 |
1041 | static Method lookup(String method) {
1042 | for (Method m : Method.values()) {
1043 | if (m.toString().equalsIgnoreCase(method)) {
1044 | return m;
1045 | }
1046 | }
1047 | return null;
1048 | }
1049 | }
1050 |
1051 | /**
1052 | * HTTP response. Return one of these from serve().
1053 | */
1054 | public static class Response implements Closeable {
1055 |
1056 | public interface IStatus {
1057 |
1058 | String getDescription();
1059 |
1060 | int getRequestStatus();
1061 | }
1062 |
1063 | /**
1064 | * Some HTTP response status codes
1065 | */
1066 | public enum Status implements IStatus {
1067 | SWITCH_PROTOCOL(101, "Switching Protocols"),
1068 | OK(200, "OK"),
1069 | CREATED(201, "Created"),
1070 | ACCEPTED(202, "Accepted"),
1071 | NO_CONTENT(204, "No Content"),
1072 | PARTIAL_CONTENT(206, "Partial Content"),
1073 | REDIRECT(301, "Moved Permanently"),
1074 | NOT_MODIFIED(304, "Not Modified"),
1075 | BAD_REQUEST(400, "Bad Request"),
1076 | UNAUTHORIZED(401, "Unauthorized"),
1077 | FORBIDDEN(403, "Forbidden"),
1078 | NOT_FOUND(404, "Not Found"),
1079 | METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
1080 | REQUEST_TIMEOUT(408, "Request Timeout"),
1081 | RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
1082 | INTERNAL_ERROR(500, "Internal Server Error"),
1083 | UNSUPPORTED_HTTP_VERSION(505, "HTTP Version Not Supported");
1084 |
1085 | private final int requestStatus;
1086 |
1087 | private final String description;
1088 |
1089 | Status(int requestStatus, String description) {
1090 | this.requestStatus = requestStatus;
1091 | this.description = description;
1092 | }
1093 |
1094 | @Override
1095 | public String getDescription() {
1096 | return "" + this.requestStatus + " " + this.description;
1097 | }
1098 |
1099 | @Override
1100 | public int getRequestStatus() {
1101 | return this.requestStatus;
1102 | }
1103 |
1104 | }
1105 |
1106 | /**
1107 | * Output stream that will automatically send every write to the wrapped
1108 | * OutputStream according to chunked transfer:
1109 | * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
1110 | */
1111 | private static class ChunkedOutputStream extends FilterOutputStream {
1112 |
1113 | public ChunkedOutputStream(OutputStream out) {
1114 | super(out);
1115 | }
1116 |
1117 | @Override
1118 | public void write(int b) throws IOException {
1119 | byte[] data = {
1120 | (byte) b
1121 | };
1122 | write(data, 0, 1);
1123 | }
1124 |
1125 | @Override
1126 | public void write(byte[] b) throws IOException {
1127 | write(b, 0, b.length);
1128 | }
1129 |
1130 | @Override
1131 | public void write(byte[] b, int off, int len) throws IOException {
1132 | if (len == 0)
1133 | return;
1134 | out.write(String.format("%x\r\n", len).getBytes());
1135 | out.write(b, off, len);
1136 | out.write("\r\n".getBytes());
1137 | }
1138 |
1139 | public void finish() throws IOException {
1140 | out.write("0\r\n\r\n".getBytes());
1141 | }
1142 |
1143 | }
1144 |
1145 | /**
1146 | * HTTP status code after processing, e.g. "200 OK", Status.OK
1147 | */
1148 | private IStatus status;
1149 |
1150 | /**
1151 | * MIME type of content, e.g. "text/html"
1152 | */
1153 | private String mimeType;
1154 |
1155 | /**
1156 | * Data of the response, may be null.
1157 | */
1158 | private InputStream data;
1159 |
1160 | private long contentLength;
1161 |
1162 | /**
1163 | * Headers for the HTTP response. Use addHeader() to add lines.
1164 | */
1165 | private final Map header = new HashMap();
1166 |
1167 | /**
1168 | * The request method that spawned this response.
1169 | */
1170 | private Method requestMethod;
1171 |
1172 | /**
1173 | * Use chunkedTransfer
1174 | */
1175 | private boolean chunkedTransfer;
1176 |
1177 | private boolean encodeAsGzip;
1178 |
1179 | private boolean keepAlive;
1180 |
1181 | /**
1182 | * Creates a fixed length response if totalBytes is not smaller than 0, otherwise chunked.
1183 | */
1184 | protected Response(IStatus status, String mimeType, InputStream data, long totalBytes) {
1185 | this.status = status;
1186 | this.mimeType = mimeType;
1187 | if (data == null) {
1188 | this.data = new ByteArrayInputStream(new byte[0]);
1189 | this.contentLength = 0L;
1190 | } else {
1191 | this.data = data;
1192 | this.contentLength = totalBytes;
1193 | }
1194 | this.chunkedTransfer = this.contentLength < 0;
1195 | keepAlive = true;
1196 | }
1197 |
1198 | @Override
1199 | public void close() throws IOException {
1200 | if (this.data != null) {
1201 | this.data.close();
1202 | }
1203 | }
1204 |
1205 | /**
1206 | * Adds given line to the header.
1207 | */
1208 | public void addHeader(String name, String value) {
1209 | this.header.put(name, value);
1210 | }
1211 |
1212 | public InputStream getData() {
1213 | return this.data;
1214 | }
1215 |
1216 | public String getHeader(String name) {
1217 | for (String headerName : header.keySet()) {
1218 | if (headerName.equalsIgnoreCase(name)) {
1219 | return header.get(headerName);
1220 | }
1221 | }
1222 | return null;
1223 | }
1224 |
1225 | public String getMimeType() {
1226 | return this.mimeType;
1227 | }
1228 |
1229 | public Method getRequestMethod() {
1230 | return this.requestMethod;
1231 | }
1232 |
1233 | public IStatus getStatus() {
1234 | return this.status;
1235 | }
1236 |
1237 | public void setGzipEncoding(boolean encodeAsGzip) {
1238 | this.encodeAsGzip = encodeAsGzip;
1239 | }
1240 |
1241 | public void setKeepAlive(boolean useKeepAlive) {
1242 | this.keepAlive = useKeepAlive;
1243 | }
1244 |
1245 | private boolean headerAlreadySent(Map header, String name) {
1246 | boolean alreadySent = false;
1247 | for (String headerName : header.keySet()) {
1248 | alreadySent |= headerName.equalsIgnoreCase(name);
1249 | }
1250 | return alreadySent;
1251 | }
1252 |
1253 | /**
1254 | * Sends given response to the socket.
1255 | */
1256 | protected void send(OutputStream outputStream) {
1257 | String mime = this.mimeType;
1258 | SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
1259 | gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
1260 |
1261 | try {
1262 | if (this.status == null) {
1263 | throw new Error("sendResponse(): Status can't be null.");
1264 | }
1265 | PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8")), false);
1266 | pw.print("HTTP/1.1 " + this.status.getDescription() + " \r\n");
1267 |
1268 | if (mime != null) {
1269 | pw.print("Content-Type: " + mime + "\r\n");
1270 | }
1271 |
1272 | if (this.header == null || this.header.get("Date") == null) {
1273 | pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
1274 | }
1275 |
1276 | if (this.header != null) {
1277 | for (String key : this.header.keySet()) {
1278 | String value = this.header.get(key);
1279 | pw.print(key + ": " + value + "\r\n");
1280 | }
1281 | }
1282 |
1283 | if (!headerAlreadySent(header, "connection")) {
1284 | pw.print("Connection: " + (this.keepAlive ? "keep-alive" : "close") + "\r\n");
1285 | }
1286 |
1287 | if (headerAlreadySent(this.header, "content-length")) {
1288 | encodeAsGzip = false;
1289 | }
1290 |
1291 | if (encodeAsGzip) {
1292 | pw.print("Content-Encoding: gzip\r\n");
1293 | setChunkedTransfer(true);
1294 | }
1295 |
1296 | long pending = this.data != null ? this.contentLength : 0;
1297 | if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
1298 | pw.print("Transfer-Encoding: chunked\r\n");
1299 | } else if (!encodeAsGzip) {
1300 | pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, this.header, pending);
1301 | }
1302 | pw.print("\r\n");
1303 | pw.flush();
1304 | sendBodyWithCorrectTransferAndEncoding(outputStream, pending);
1305 | outputStream.flush();
1306 | safeClose(this.data);
1307 | } catch (IOException ioe) {
1308 | NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);
1309 | }
1310 | }
1311 |
1312 | private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException {
1313 | if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
1314 | ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream);
1315 | sendBodyWithCorrectEncoding(chunkedOutputStream, -1);
1316 | chunkedOutputStream.finish();
1317 | } else {
1318 | sendBodyWithCorrectEncoding(outputStream, pending);
1319 | }
1320 | }
1321 |
1322 | private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {
1323 | if (encodeAsGzip) {
1324 | GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
1325 | sendBody(gzipOutputStream, -1);
1326 | gzipOutputStream.finish();
1327 | } else {
1328 | sendBody(outputStream, pending);
1329 | }
1330 | }
1331 |
1332 | /**
1333 | * Sends the body to the specified OutputStream. The pending parameter
1334 | * limits the maximum amounts of bytes sent unless it is -1, in which
1335 | * case everything is sent.
1336 | *
1337 | * @param outputStream
1338 | * the OutputStream to send data to
1339 | * @param pending
1340 | * -1 to send everything, otherwise sets a max limit to the
1341 | * number of bytes sent
1342 | * @throws IOException
1343 | * if something goes wrong while sending the data.
1344 | */
1345 | private void sendBody(OutputStream outputStream, long pending) throws IOException {
1346 | long BUFFER_SIZE = 16 * 1024;
1347 | byte[] buff = new byte[(int) BUFFER_SIZE];
1348 | boolean sendEverything = pending == -1;
1349 | while (pending > 0 || sendEverything) {
1350 | long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE);
1351 | int read = this.data.read(buff, 0, (int) bytesToRead);
1352 | if (read <= 0) {
1353 | break;
1354 | }
1355 | outputStream.write(buff, 0, read);
1356 | if (!sendEverything) {
1357 | pending -= read;
1358 | }
1359 | }
1360 | }
1361 |
1362 | protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map header, long size) {
1363 | for (String headerName : header.keySet()) {
1364 | if (headerName.equalsIgnoreCase("content-length")) {
1365 | try {
1366 | return Long.parseLong(header.get(headerName));
1367 | } catch (NumberFormatException ex) {
1368 | return size;
1369 | }
1370 | }
1371 | }
1372 |
1373 | pw.print("Content-Length: " + size + "\r\n");
1374 | return size;
1375 | }
1376 |
1377 | public void setChunkedTransfer(boolean chunkedTransfer) {
1378 | this.chunkedTransfer = chunkedTransfer;
1379 | }
1380 |
1381 | public void setData(InputStream data) {
1382 | this.data = data;
1383 | }
1384 |
1385 | public void setMimeType(String mimeType) {
1386 | this.mimeType = mimeType;
1387 | }
1388 |
1389 | public void setRequestMethod(Method requestMethod) {
1390 | this.requestMethod = requestMethod;
1391 | }
1392 |
1393 | public void setStatus(IStatus status) {
1394 | this.status = status;
1395 | }
1396 | }
1397 |
1398 | public static final class ResponseException extends Exception {
1399 |
1400 | private static final long serialVersionUID = 6569838532917408380L;
1401 |
1402 | private final Response.Status status;
1403 |
1404 | public ResponseException(Response.Status status, String message) {
1405 | super(message);
1406 | this.status = status;
1407 | }
1408 |
1409 | public ResponseException(Response.Status status, String message, Exception e) {
1410 | super(message, e);
1411 | this.status = status;
1412 | }
1413 |
1414 | public Response.Status getStatus() {
1415 | return this.status;
1416 | }
1417 | }
1418 |
1419 | /**
1420 | * The runnable that will be used for the main listening thread.
1421 | */
1422 | public class ServerRunnable implements Runnable {
1423 |
1424 | private final int timeout;
1425 |
1426 | private IOException bindException;
1427 |
1428 | private boolean hasBinded = false;
1429 |
1430 | private ServerRunnable(int timeout) {
1431 | this.timeout = timeout;
1432 | }
1433 |
1434 | @Override
1435 | public void run() {
1436 | try {
1437 | myServerSocket.bind(hostname != null ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
1438 | hasBinded = true;
1439 | } catch (IOException e) {
1440 | this.bindException = e;
1441 | return;
1442 | }
1443 | do {
1444 | try {
1445 | final Socket finalAccept = NanoHTTPD.this.myServerSocket.accept();
1446 | if (this.timeout > 0) {
1447 | finalAccept.setSoTimeout(this.timeout);
1448 | }
1449 | final InputStream inputStream = finalAccept.getInputStream();
1450 | NanoHTTPD.this.asyncRunner.exec(createClientHandler(finalAccept, inputStream));
1451 | } catch (IOException e) {
1452 | NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);
1453 | }
1454 | } while (!NanoHTTPD.this.myServerSocket.isClosed());
1455 | }
1456 | }
1457 |
1458 | /**
1459 | * A temp file.
1460 |
1461 | *
1462 | * Temp files are responsible for managing the actual temporary storage and
1463 | * cleaning themselves up when no longer needed.
1464 | *
1465 | */
1466 | public interface TempFile {
1467 |
1468 | void delete() throws Exception;
1469 |
1470 | String getName();
1471 |
1472 | OutputStream open() throws Exception;
1473 | }
1474 |
1475 | /**
1476 | * Temp file manager.
1477 |
1478 | *
1479 | * Temp file managers are created 1-to-1 with incoming requests, to create
1480 | * and cleanup temporary files created as a result of handling the request.
1481 | *
1482 | */
1483 | public interface TempFileManager {
1484 |
1485 | void clear();
1486 |
1487 | TempFile createTempFile() throws Exception;
1488 | }
1489 |
1490 | /**
1491 | * Factory to create temp file managers.
1492 | */
1493 | public interface TempFileManagerFactory {
1494 |
1495 | TempFileManager create();
1496 | }
1497 |
1498 | /**
1499 | * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
1500 | * This is required as the Keep-Alive HTTP connections would otherwise block
1501 | * the socket reading thread forever (or as long the browser is open).
1502 | */
1503 | public static final int SOCKET_READ_TIMEOUT = 5000;
1504 |
1505 | /**
1506 | * Common MIME type for dynamic content: plain text
1507 | */
1508 | public static final String MIME_PLAINTEXT = "text/plain";
1509 |
1510 | /**
1511 | * Common MIME type for dynamic content: html
1512 | */
1513 | public static final String MIME_HTML = "text/html";
1514 |
1515 | /**
1516 | * Pseudo-Parameter to use to store the actual query string in the
1517 | * parameters map for later re-processing.
1518 | */
1519 | private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
1520 |
1521 | /**
1522 | * logger to log to.
1523 | */
1524 | private static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName());
1525 |
1526 | /**
1527 | * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an
1528 | * array of loaded KeyManagers. These objects must properly
1529 | * loaded/initialized by the caller.
1530 | */
1531 | public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException {
1532 | SSLServerSocketFactory res = null;
1533 | try {
1534 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
1535 | trustManagerFactory.init(loadedKeyStore);
1536 | SSLContext ctx = SSLContext.getInstance("TLS");
1537 | ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null);
1538 | res = ctx.getServerSocketFactory();
1539 | } catch (Exception e) {
1540 | throw new IOException(e.getMessage());
1541 | }
1542 | return res;
1543 | }
1544 |
1545 | /**
1546 | * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a
1547 | * loaded KeyManagerFactory. These objects must properly loaded/initialized
1548 | * by the caller.
1549 | */
1550 | public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException {
1551 | SSLServerSocketFactory res = null;
1552 | try {
1553 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
1554 | trustManagerFactory.init(loadedKeyStore);
1555 | SSLContext ctx = SSLContext.getInstance("TLS");
1556 | ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
1557 | res = ctx.getServerSocketFactory();
1558 | } catch (Exception e) {
1559 | throw new IOException(e.getMessage());
1560 | }
1561 | return res;
1562 | }
1563 |
1564 | /**
1565 | * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your
1566 | * certificate and passphrase
1567 | */
1568 | public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException {
1569 | SSLServerSocketFactory res = null;
1570 | try {
1571 | KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
1572 | InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);
1573 | keystore.load(keystoreStream, passphrase);
1574 | TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
1575 | trustManagerFactory.init(keystore);
1576 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
1577 | keyManagerFactory.init(keystore, passphrase);
1578 | SSLContext ctx = SSLContext.getInstance("TLS");
1579 | ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
1580 | res = ctx.getServerSocketFactory();
1581 | } catch (Exception e) {
1582 | throw new IOException(e.getMessage());
1583 | }
1584 | return res;
1585 | }
1586 |
1587 | private static final void safeClose(Object closeable) {
1588 | try {
1589 | if (closeable != null) {
1590 | if (closeable instanceof Closeable) {
1591 | ((Closeable) closeable).close();
1592 | } else if (closeable instanceof Socket) {
1593 | ((Socket) closeable).close();
1594 | } else if (closeable instanceof ServerSocket) {
1595 | ((ServerSocket) closeable).close();
1596 | } else {
1597 | throw new IllegalArgumentException("Unknown object to close");
1598 | }
1599 | }
1600 | } catch (IOException e) {
1601 | NanoHTTPD.LOG.log(Level.SEVERE, "Could not close", e);
1602 | }
1603 | }
1604 |
1605 | private final String hostname;
1606 |
1607 | private final int myPort;
1608 |
1609 | private ServerSocket myServerSocket;
1610 |
1611 | private SSLServerSocketFactory sslServerSocketFactory;
1612 |
1613 | private Thread myThread;
1614 |
1615 | /**
1616 | * Pluggable strategy for asynchronously executing requests.
1617 | */
1618 | protected AsyncRunner asyncRunner;
1619 |
1620 | /**
1621 | * Pluggable strategy for creating and cleaning up temporary files.
1622 | */
1623 | private TempFileManagerFactory tempFileManagerFactory;
1624 |
1625 | /**
1626 | * Constructs an HTTP server on given port.
1627 | */
1628 | public NanoHTTPD(int port) {
1629 | this(null, port);
1630 | }
1631 |
1632 | // -------------------------------------------------------------------------------
1633 | // //
1634 | //
1635 | // Threading Strategy.
1636 | //
1637 | // -------------------------------------------------------------------------------
1638 | // //
1639 |
1640 | /**
1641 | * Constructs an HTTP server on given hostname and port.
1642 | */
1643 | public NanoHTTPD(String hostname, int port) {
1644 | this.hostname = hostname;
1645 | this.myPort = port;
1646 | setTempFileManagerFactory(new DefaultTempFileManagerFactory());
1647 | setAsyncRunner(new DefaultAsyncRunner());
1648 | }
1649 |
1650 | /**
1651 | * Forcibly closes all connections that are open.
1652 | */
1653 | public synchronized void closeAllConnections() {
1654 | stop();
1655 | }
1656 |
1657 | /**
1658 | * create a instance of the client handler, subclasses can return a subclass
1659 | * of the ClientHandler.
1660 | *
1661 | * @param finalAccept
1662 | * the socket the cleint is connected to
1663 | * @param inputStream
1664 | * the input stream
1665 | * @return the client handler
1666 | */
1667 | protected ClientHandler createClientHandler(final Socket finalAccept, final InputStream inputStream) {
1668 | return new ClientHandler(inputStream, finalAccept);
1669 | }
1670 |
1671 | /**
1672 | * Instantiate the server runnable, can be overwritten by subclasses to
1673 | * provide a subclass of the ServerRunnable.
1674 | *
1675 | * @param timeout
1676 | * the socet timeout to use.
1677 | * @return the server runnable.
1678 | */
1679 | protected ServerRunnable createServerRunnable(final int timeout) {
1680 | return new ServerRunnable(timeout);
1681 | }
1682 |
1683 | /**
1684 | * Decode parameters from a URL, handing the case where a single parameter
1685 | * name might have been supplied several times, by return lists of values.
1686 | * In general these lists will contain a single element.
1687 | *
1688 | * @param parms
1689 | * original NanoHTTPD parameters values, as passed to the
1690 | * serve()
method.
1691 | * @return a map of String
(parameter name) to
1692 | * List<String>
(a list of the values supplied).
1693 | */
1694 | protected Map> decodeParameters(Map parms) {
1695 | return this.decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER));
1696 | }
1697 |
1698 | // -------------------------------------------------------------------------------
1699 | // //
1700 |
1701 | /**
1702 | * Decode parameters from a URL, handing the case where a single parameter
1703 | * name might have been supplied several times, by return lists of values.
1704 | * In general these lists will contain a single element.
1705 | *
1706 | * @param queryString
1707 | * a query string pulled from the URL.
1708 | * @return a map of String
(parameter name) to
1709 | * List<String>
(a list of the values supplied).
1710 | */
1711 | protected Map> decodeParameters(String queryString) {
1712 | Map> parms = new HashMap>();
1713 | if (queryString != null) {
1714 | StringTokenizer st = new StringTokenizer(queryString, "&");
1715 | while (st.hasMoreTokens()) {
1716 | String e = st.nextToken();
1717 | int sep = e.indexOf('=');
1718 | String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();
1719 | if (!parms.containsKey(propertyName)) {
1720 | parms.put(propertyName, new ArrayList());
1721 | }
1722 | String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null;
1723 | if (propertyValue != null) {
1724 | parms.get(propertyName).add(propertyValue);
1725 | }
1726 | }
1727 | }
1728 | return parms;
1729 | }
1730 |
1731 | /**
1732 | * Decode percent encoded String
values.
1733 | *
1734 | * @param str
1735 | * the percent encoded String
1736 | * @return expanded form of the input, for example "foo%20bar" becomes
1737 | * "foo bar"
1738 | */
1739 | protected String decodePercent(String str) {
1740 | String decoded = null;
1741 | try {
1742 | decoded = URLDecoder.decode(str, "UTF8");
1743 | } catch (UnsupportedEncodingException ignored) {
1744 | NanoHTTPD.LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored);
1745 | }
1746 | return decoded;
1747 | }
1748 |
1749 | /**
1750 | * @return true if the gzip compression should be used if the client
1751 | * accespts it. Default this option is on for text content and off
1752 | * for everything else.
1753 | */
1754 | protected boolean useGzipWhenAccepted(Response r) {
1755 | return r.getMimeType() != null && r.getMimeType().toLowerCase().contains("text/");
1756 | }
1757 |
1758 | public final int getListeningPort() {
1759 | return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort();
1760 | }
1761 |
1762 | public final boolean isAlive() {
1763 | return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive();
1764 | }
1765 |
1766 | /**
1767 | * Call before start() to serve over HTTPS instead of HTTP
1768 | */
1769 | public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) {
1770 | this.sslServerSocketFactory = sslServerSocketFactory;
1771 | }
1772 |
1773 | /**
1774 | * Create a response with unknown length (using HTTP 1.1 chunking).
1775 | */
1776 | public Response newChunkedResponse(Response.IStatus status, String mimeType, InputStream data) {
1777 | return new Response(status, mimeType, data, -1);
1778 | }
1779 |
1780 | /**
1781 | * Create a response with known length.
1782 | */
1783 | public Response newFixedLengthResponse(Response.IStatus status, String mimeType, InputStream data, long totalBytes) {
1784 | return new Response(status, mimeType, data, totalBytes);
1785 | }
1786 |
1787 | /**
1788 | * Create a text response with known length.
1789 | */
1790 | public Response newFixedLengthResponse(Response.IStatus status, String mimeType, String txt) {
1791 | if (txt == null) {
1792 | return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);
1793 | } else {
1794 | byte[] bytes;
1795 | try {
1796 | bytes = txt.getBytes("UTF-8");
1797 | } catch (UnsupportedEncodingException e) {
1798 | NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e);
1799 | bytes = new byte[0];
1800 | }
1801 | return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(bytes), bytes.length);
1802 | }
1803 | }
1804 |
1805 | /**
1806 | * Create a text response with known length.
1807 | */
1808 | public Response newFixedLengthResponse(String msg) {
1809 | return newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_HTML, msg);
1810 | }
1811 |
1812 | /**
1813 | * Override this to customize the server.
1814 | * (By default, this returns a 404 "Not Found" plain text error response.)
1815 | *
1816 | * @param session
1817 | * The HTTP session
1818 | * @return HTTP response, see class Response for details
1819 | */
1820 | public Response serve(IHTTPSession session) {
1821 | Map files = new HashMap();
1822 | Method method = session.getMethod();
1823 | if (Method.PUT.equals(method) || Method.POST.equals(method)) {
1824 | try {
1825 | session.parseBody(files);
1826 | } catch (IOException ioe) {
1827 | return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
1828 | } catch (ResponseException re) {
1829 | return newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
1830 | }
1831 | }
1832 |
1833 | Map parms = session.getParms();
1834 | parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString());
1835 | return serve(session.getUri(), method, session.getHeaders(), parms, files);
1836 | }
1837 |
1838 | /**
1839 | * Override this to customize the server.
1840 |
1841 |
1842 | * (By default, this returns a 404 "Not Found" plain text error response.)
1843 | *
1844 | * @param uri
1845 | * Percent-decoded URI without parameters, for example
1846 | * "/index.cgi"
1847 | * @param method
1848 | * "GET", "POST" etc.
1849 | * @param parms
1850 | * Parsed, percent decoded parameters from URI and, in case of
1851 | * POST, data.
1852 | * @param headers
1853 | * Header entries, percent decoded
1854 | * @return HTTP response, see class Response for details
1855 | */
1856 | @Deprecated
1857 | public Response serve(String uri, Method method, Map headers, Map parms, Map files) {
1858 | return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");
1859 | }
1860 |
1861 | /**
1862 | * Pluggable strategy for asynchronously executing requests.
1863 | *
1864 | * @param asyncRunner
1865 | * new strategy for handling threads.
1866 | */
1867 | public void setAsyncRunner(AsyncRunner asyncRunner) {
1868 | this.asyncRunner = asyncRunner;
1869 | }
1870 |
1871 | /**
1872 | * Pluggable strategy for creating and cleaning up temporary files.
1873 | *
1874 | * @param tempFileManagerFactory
1875 | * new strategy for handling temp files.
1876 | */
1877 | public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {
1878 | this.tempFileManagerFactory = tempFileManagerFactory;
1879 | }
1880 |
1881 | /**
1882 | * Start the server.
1883 | *
1884 | * @throws IOException
1885 | * if the socket is in use.
1886 | */
1887 | public void start() throws IOException {
1888 | start(NanoHTTPD.SOCKET_READ_TIMEOUT);
1889 | }
1890 |
1891 | /**
1892 | * Start the server.
1893 | *
1894 | * @param timeout
1895 | * timeout to use for socket connections.
1896 | * @throws IOException
1897 | * if the socket is in use.
1898 | */
1899 | public void start(final int timeout) throws IOException {
1900 | if (this.sslServerSocketFactory != null) {
1901 | SSLServerSocket ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();
1902 | ss.setNeedClientAuth(false);
1903 | this.myServerSocket = ss;
1904 | } else {
1905 | this.myServerSocket = new ServerSocket();
1906 | }
1907 | this.myServerSocket.setReuseAddress(true);
1908 |
1909 | ServerRunnable serverRunnable = createServerRunnable(timeout);
1910 | this.myThread = new Thread(serverRunnable);
1911 | this.myThread.setDaemon(true);
1912 | this.myThread.setName("NanoHttpd Main Listener");
1913 | this.myThread.start();
1914 | while (!serverRunnable.hasBinded && serverRunnable.bindException == null) {
1915 | try {
1916 | Thread.sleep(10L);
1917 | } catch (Throwable e) {
1918 | // on android this may not be allowed, that's why we
1919 | // catch throwable the wait should be very short because we are
1920 | // just waiting for the bind of the socket
1921 | }
1922 | }
1923 | if (serverRunnable.bindException != null) {
1924 | throw serverRunnable.bindException;
1925 | }
1926 | }
1927 |
1928 | /**
1929 | * Stop the server.
1930 | */
1931 | public void stop() {
1932 | try {
1933 | safeClose(this.myServerSocket);
1934 | this.asyncRunner.closeAll();
1935 | if (this.myThread != null) {
1936 | this.myThread.join();
1937 | }
1938 | } catch (Exception e) {
1939 | NanoHTTPD.LOG.log(Level.SEVERE, "Could not stop all connections", e);
1940 | }
1941 | }
1942 |
1943 | public final boolean wasStarted() {
1944 | return this.myServerSocket != null && this.myThread != null;
1945 | }
1946 | }
--------------------------------------------------------------------------------
/awebdb/src/main/java/com/lusfold/awebdb/net/nanohttpd/XNanoHTTPD.java:
--------------------------------------------------------------------------------
1 | package com.lusfold.awebdb.net.nanohttpd;
2 |
3 | import com.lusfold.awebdb.utils.LogUtil;
4 | import com.lusfold.awebdb.db.DBUtil;
5 | import com.lusfold.awebdb.mapper.HtmlMapper;
6 |
7 | import java.net.URLDecoder;
8 |
9 | /**
10 | * @author Lusfold
11 | */
12 | public class XNanoHTTPD extends NanoHTTPD {
13 |
14 | /**
15 | * @param port
16 | */
17 | public XNanoHTTPD(int port) {
18 | super(port);
19 | }
20 |
21 | /**
22 | * @param hostname
23 | * @param port
24 | */
25 | public XNanoHTTPD(String hostname, int port) {
26 | super(hostname, port);
27 | }
28 |
29 | /**
30 | * @param session The HTTP session
31 | * @return
32 | */
33 | public Response serve(IHTTPSession session) {
34 | Method method = session.getMethod();
35 | LogUtil.d(method.toString());
36 | if (NanoHTTPD.Method.GET.equals(method)) {
37 | //get method
38 | String queryParams = session.getQueryParameterString();
39 | queryParams = URLDecoder.decode(queryParams);
40 | HtmlMapper htmlMapper = new HtmlMapper();
41 | DBUtil.inflateMapper(queryParams, htmlMapper);
42 | LogUtil.d(htmlMapper.map());
43 | return new HtmlResponse(htmlMapper);
44 | } else if (NanoHTTPD.Method.POST.equals(method)) {
45 | //post method
46 | HtmlMapper htmlMapper = new HtmlMapper();
47 | htmlMapper.addHeadInFo("Don't support post method");
48 | LogUtil.d(htmlMapper.map());
49 | return new HtmlResponse(htmlMapper);
50 | }
51 | return super.serve(session);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/awebdb/src/main/java/com/lusfold/awebdb/utils/LogUtil.java:
--------------------------------------------------------------------------------
1 | package com.lusfold.awebdb.utils;
2 |
3 | import android.util.Log;
4 |
5 | import java.text.SimpleDateFormat;
6 | import java.util.Date;
7 |
8 | public class LogUtil {
9 | public static boolean DEBUG = true;
10 |
11 | public static void d(String TAG, String method, String msg) {
12 | Log.d(TAG, "[" + method + "]" + msg);
13 | }
14 |
15 | public static void d(String TAG, String msg){
16 | if (DEBUG) {
17 | Log.d(TAG, "[" + getFileLineMethod() + "]" + msg);
18 | }
19 | }
20 |
21 | public static void d(String msg){
22 | if (DEBUG) {
23 | Log.d(_FILE_(), "[" + getLineMethod() + "]" + msg);
24 | }
25 | }
26 |
27 | public static void e(String msg){
28 | if (DEBUG) {
29 | Log.e(_FILE_(), getLineMethod() + msg);
30 | }
31 | }
32 |
33 | public static void e(String TAG, String msg){
34 | if (DEBUG) {
35 | Log.e(TAG, getLineMethod() + msg);
36 | }
37 | }
38 |
39 | public static String getFileLineMethod() {
40 | StackTraceElement traceElement = ((new Exception()).getStackTrace())[2];
41 | StringBuffer toStringBuffer = new StringBuffer("[")
42 | .append(traceElement.getFileName()).append(" | ")
43 | .append(traceElement.getLineNumber()).append(" | ")
44 | .append(traceElement.getMethodName()).append("]");
45 | return toStringBuffer.toString();
46 | }
47 |
48 | public static String getLineMethod() {
49 | StackTraceElement traceElement = ((new Exception()).getStackTrace())[2];
50 | StringBuffer toStringBuffer = new StringBuffer("[")
51 | .append(traceElement.getLineNumber()).append(" | ")
52 | .append(traceElement.getMethodName()).append("]");
53 | return toStringBuffer.toString();
54 | }
55 |
56 | public static String _FILE_() {
57 | StackTraceElement traceElement = ((new Exception()).getStackTrace())[2];
58 | return traceElement.getFileName();
59 | }
60 |
61 | public static String _FUNC_() {
62 | StackTraceElement traceElement = ((new Exception()).getStackTrace())[1];
63 | return traceElement.getMethodName();
64 | }
65 |
66 | public static int _LINE_() {
67 | StackTraceElement traceElement = ((new Exception()).getStackTrace())[1];
68 | return traceElement.getLineNumber();
69 | }
70 |
71 | public static String _TIME_() {
72 | Date now = new Date();
73 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
74 | return sdf.format(now);
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/awebdb/src/main/java/com/lusfold/awebdb/utils/NetUtils.java:
--------------------------------------------------------------------------------
1 | package com.lusfold.awebdb.utils;
2 |
3 | import org.apache.http.conn.util.InetAddressUtils;
4 |
5 | import java.net.InetAddress;
6 | import java.net.NetworkInterface;
7 | import java.net.SocketException;
8 | import java.util.Enumeration;
9 |
10 | /**
11 | * @author Lusfold
12 |
13 | */
14 | public class NetUtils {
15 | public static final int DefaultPort = 8080;
16 |
17 | /**
18 | * @param port
19 | * @return a legally port.
20 | */
21 | public static int getAGoodPort(int port) {
22 | return port < 1023 || port > 65535 ? DefaultPort : port;
23 | }
24 |
25 | /**
26 | * @return local ip address
27 | */
28 | public static String getLocalIpAddress() {
29 | try {
30 | Enumeration infos = NetworkInterface
31 | .getNetworkInterfaces();
32 | while (infos.hasMoreElements()) {
33 | NetworkInterface niFace = infos.nextElement();
34 | Enumeration enumIpAddr = niFace.getInetAddresses();
35 | while (enumIpAddr.hasMoreElements()) {
36 | InetAddress mInetAddress = enumIpAddr.nextElement();
37 | if (!mInetAddress.isLoopbackAddress()
38 | && InetAddressUtils.isIPv4Address(mInetAddress
39 | .getHostAddress())) {
40 | return mInetAddress.getHostAddress().toString();
41 | }
42 | }
43 | }
44 | } catch (SocketException e) {
45 |
46 | }
47 | return null;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/awebdb/src/main/java/com/lusfold/awebdb/utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.lusfold.awebdb.utils;
2 |
3 | /**
4 | * @author Lusfold
5 | */
6 | public class StringUtils {
7 | /**
8 | * @param str
9 | * @return if string is null or it's length equals 0 except space,return true,else return false.
10 | */
11 | public static boolean isBlank(String str) {
12 | return (str == null || str.trim().length() == 0);
13 | }
14 |
15 | /**
16 | * @param str
17 | * @return if string is not null and it's length do not equals 0 except space,return true,else return false.
18 | */
19 | public static boolean isNotBlank(String str) {
20 | return !(str == null || str.trim().length() == 0);
21 | }
22 |
23 | /**
24 | * @param str
25 | * @return if string is null or it's length equals 0,return true,else return false.
26 | */
27 | public static boolean isEmpty(CharSequence str) {
28 | return (str == null || str.length() == 0);
29 | }
30 |
31 | /**
32 | * @param str
33 | * @return if string is not null and it's length do not equals 0,return true,else return false.
34 | */
35 | public static boolean isNotEmpty(CharSequence str) {
36 | return !(str == null || str.length() == 0);
37 | }
38 |
39 | /**
40 | * @param str
41 | * @return if sql string is not empty and starts with select.
42 | */
43 | public static boolean isSelectStr(String str) {
44 | return (isNotEmpty(str) && str.toUpperCase().startsWith("SELECT"));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/awebdb/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Method_Unsupported
3 | Method_Unsupported
4 |
5 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | jcenter()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:1.2.3'
8 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lusfold/AWebDB-Sample/1d82512bcd9280cee5e1383e6e3189d7fd2bd396/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jul 13 22:31:23 CST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':awebdb'
2 |
--------------------------------------------------------------------------------