เซิร์ฟเวอร์ HTTP อย่างง่ายใน Java โดยใช้ Java SE API เท่านั้น


333

มีวิธีการสร้างเซิร์ฟเวอร์ HTTP พื้นฐานมาก (รองรับ GET / POST เท่านั้น) ใน Java โดยใช้ Java SE API โดยไม่ต้องเขียนโค้ดเพื่อแยกคำขอ HTTP ด้วยตนเองและจัดรูปแบบการตอบสนอง HTTP ด้วยตนเองหรือไม่ Java SE API สรุปการทำงานของไคลเอนต์ HTTP ใน HttpURLConnection เป็นอย่างดี แต่มีระบบอนาล็อกสำหรับการทำงานของเซิร์ฟเวอร์ HTTP หรือไม่

เพียงเพื่อให้ชัดเจนปัญหาที่ฉันมีกับตัวอย่าง ServerSocket มากมายที่ฉันเคยเห็นทางออนไลน์คือพวกเขาทำการจัดรูปแบบคำขอ / การตอบสนองการแยกวิเคราะห์และการจัดการข้อผิดพลาดของตัวเองซึ่งน่าเบื่อผิดพลาดง่ายและไม่ครอบคลุม และฉันพยายามหลีกเลี่ยงด้วยเหตุผลเหล่านั้น

เป็นตัวอย่างของการจัดการ HTTP ด้วยตนเองที่ฉันพยายามหลีกเลี่ยง:

http://java.sun.com/developer/technicalArticles/Networking/Webserver/WebServercode.html


3
อืมม ... คำตอบสั้น ๆ คือไม่ หากคุณต้องการบางสิ่งที่จัดการโพสต์และรับคำขอโดยไม่ต้องเขียนส่วนหัว http ด้วยตนเองคุณสามารถใช้ servlets ได้ แต่นั่นคือจาวา ee หากคุณไม่ต้องการใช้บางอย่างเช่นนั้นแล้วซ็อกเก็ตและการวิเคราะห์คำด้วยตนเองเป็นตัวเลือกอื่นที่ฉันรู้จัก
Matt Phillips

3
ฉันรู้ว่านี่ไม่ได้อยู่ในจิตวิญญาณของ SO แต่ฉันอยากให้คุณพิจารณาว่าคุณไม่ชอบ Java API ของ Java ตามคำตอบบางส่วนที่กล่าวถึงมีการใช้งานตรงไปตรงมาบางอย่างเช่น Jetty ที่ให้คุณสามารถฝังเว็บเซิร์ฟเวอร์ในแอปพลิเคชันแบบสแตนด์อะโลนของคุณในขณะที่ยังคงใช้ประโยชน์จาก servlet api ถ้าคุณอย่างไม่สามารถใช้งาน Java EE API สำหรับเหตุผลบางกว่าโปรดเพิกเฉยต่อความคิดเห็นของฉัน :-)
คริส ธ อมป์สัน

1
"Servlets" ไม่ใช่ "Java EE" จริงๆ พวกเขาเป็นเพียงวิธีการเขียนปลั๊กอินที่สามารถเรียกใช้โดยแอปพลิเคชันที่อยู่รายล้อมเพื่อตอบสนองต่อกิจกรรมข้อความ การจัดเตรียมสภาพแวดล้อมการโฮสต์เซิร์ฟเล็ต "การใช้เพียง Java SE API" เป็นสิ่งที่ Jetty และ Tomcat ทำ แน่นอนว่าคุณอาจต้องการขจัดความซับซ้อนที่ไม่ต้องการออกไปแต่คุณอาจต้องตัดสินใจเลือกชุดย่อยของคุณลักษณะและการกำหนดค่าที่อนุญาตของ GET / POST มักจะไม่คุ้มค่ายกเว้นเรื่องความปลอดภัย / ปัญหาแบบฝัง
David Tonhofer

1
มันอาจจะคุ้มค่าที่จะต้องผ่านรายการเซิร์ฟเวอร์ http นี้ก่อนตัดสินใจ java-source.net/open-source/web-servers
ThreaT

คำตอบ:


469

ตั้งแต่ Java SE 6 มีเซิร์ฟเวอร์ HTTP ในตัวในSun Oracle JRE com.sun.net.httpserverสรุปแพคเกจแสดงชั้นเรียนมีส่วนร่วมและมีตัวอย่าง

นี่คือตัวอย่างเขี่ยcopypastedจากเอกสารของพวกเขา (ทุกคนพยายามที่จะแก้ไข แต่อย่างไรก็ตามมันเพราะมันเป็นชิ้นส่วนที่น่าเกลียดของรหัสโปรดอย่านี้เป็นวางสำเนาไม่ระเบิดนอกจากนี้คุณไม่ควรแก้ไขใบเสนอราคาจนกว่าพวกเขาจะมีการเปลี่ยนแปลง ในแหล่งต้นฉบับ) คุณสามารถ copy'n'paste'n'run บน Java 6+

package com.stackoverflow.q3732109;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class Test {

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/test", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    }

    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response";
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }

}

ตั้งข้อสังเกตที่ควรจะเป็นว่าเป็นส่วนหนึ่งในตัวอย่างของพวกเขาไม่ดีก็ควรจะได้รับresponse.length() response.getBytes().lengthแม้ในตอนนั้นgetBytes()วิธีการจะต้องระบุชุดอักขระที่คุณระบุในส่วนหัวการตอบกลับอย่างชัดเจน อนิจจาแม้ว่าจะมีการเข้าใจผิดที่จะเริ่มมันก็เป็นเพียงตัวอย่างเขี่ยพื้นฐาน

ดำเนินการแล้วไปที่http: // localhost: 8000 / testและคุณจะเห็นคำตอบต่อไปนี้:

นี่คือการตอบสนอง


เป็นไปโดยใช้com.sun.*การเรียนทำทราบว่านี้คือในตรงกันข้ามกับสิ่งที่นักพัฒนาบางคนคิดว่าอย่างไม่ต้องห้ามตามที่รู้จักกันดีคำถามที่พบบ่อยทำไมนักพัฒนาโปรแกรมไม่ควรเขียนว่าโทร 'อาทิตย์' แพคเกจ คำถามที่พบบ่อยนั้นเกี่ยวข้องกับsun.*แพ็คเกจ (เช่นsun.misc.BASE64Encoder) สำหรับการใช้งานภายในโดย Oracle JRE (ซึ่งจะทำให้แอปพลิเคชันของคุณหยุดทำงานเมื่อคุณเรียกใช้บน JRE อื่น) ไม่ใช่com.sun.*แพ็คเกจ Sun / Oracle ยังเพิ่งพัฒนาซอฟต์แวร์บน Java SE API เองเหมือนทุก บริษัท อื่น ๆ เช่น Apache เป็นต้น การใช้com.sun.*คลาสนั้นไม่สนับสนุน (แต่ห้ามไม่ให้ใช้) เมื่อมันเกี่ยวข้องกับการใช้งาน Java API บางตัวเช่น GlassFish (Java EE impl), Mojarra (JSF impl), Jersey (JAX-RS impl) เป็นต้น


19
@Waldheinz: เหมือน @Software คุณกำลังสับสนกับsun.* com.sun.*ตัวอย่างเช่นคุณเห็นเอกสารของsun.*API หรือไม่ ดูที่นี่: java.sun.com/products/jdk/faq/faq-sun-packages.htmlมันบอกอะไรเกี่ยวกับcom.sun.*? com.sun.*เป็นเพียงการใช้ซอฟแวร์สำหรับประชาชนของตัวเองซึ่งไม่ได้เป็นส่วนหนึ่งของ Java API พวกเขายังพัฒนาซอฟต์แวร์บน Java API เช่นเดียวกับ บริษัท อื่น ๆ ทุกแห่ง
BalusC

4
ฉันคิดว่านี่เป็นเซิร์ฟเวอร์ http ที่ดีมากสำหรับใช้ในกรณีทดสอบการรวมระบบ ขอบคุณสำหรับคำใบ้!
Andreas Petersson

13
หากคุณกำลังใช้ Eclipse และได้รับข้อผิดพลาดเช่น "การ จำกัด การเข้าถึง: ประเภท HttpExchange ไม่สามารถเข้าถึงได้เนื่องจากข้อ จำกัด ในห้องสมุดที่ต้องการ ... ", stackoverflow.com/a/10642163บอกวิธีปิดการใช้งานการตรวจสอบการเข้าถึง
Samuli Pahaoja

13
FWIW นี้ยังมีอยู่ใน OpenJDK
Jason C

6
คลาสที่อ้างถึงที่นี่ถูกติดแท็ก@jdk.Exportedในซอร์สโค้ดของ OpenJDK ซึ่งหมายความว่า API นั้นถือว่าเป็นสาธารณะและจะมีให้ใน Java 9 ( com.sun.*แพ็คเกจอื่น ๆจะไม่สามารถใช้งานได้เนื่องจาก Project Jigsaw)
จูลส์

42

ตรวจสอบNanoHttpd

"NanoHTTPD เป็นเซิร์ฟเวอร์ HTTP น้ำหนักเบาที่ออกแบบมาสำหรับการฝังในแอปพลิเคชันอื่น ๆ ซึ่งเผยแพร่ภายใต้ลิขสิทธิ์ดัดแปลง BSD

มันกำลังพัฒนาที่ Github และใช้ Apache Maven สำหรับการสร้างและทดสอบหน่วย "


4
ข้อควรระวังอย่างหนึ่ง: เป็นไปได้ว่า NanoHTTPD ไม่มีการป้องกันการโจมตีจากการเดินบนต้นไม้ - คุณควรตรวจสอบสิ่งนี้หากจะให้บริการในที่อยู่สาธารณะ ด้วยวิธีนี้ฉันหมายถึงการโจมตีที่มีการออกคำขอเช่นGET /../../blahblah http/1.1นั้นและเซิร์ฟเวอร์จะเดินไปเหนือรูทเว็บไซต์และเข้าสู่พื้นที่ไฟล์ระบบการให้บริการไฟล์ที่สามารถใช้ในการประนีประนอมหรือโจมตีระบบจากระยะไกลเช่นไฟล์รหัสผ่าน
Lawrence Dol

7
ดูเหมือนว่าจะได้รับการแก้ไข รุ่นปัจจุบันสร้าง 403 ถ้า (uri.startsWith (".. ") || uri.endsWith (".. ") || uri.indexOf ("../")> = 0)
Lena Schimmel

5
ฉันไม่เข้าใจว่านี่เป็นคำตอบสำหรับคำถามนี้อย่างไร
kimathie

28

com.sun.net.httpserverแก้ปัญหาคือไม่พกพาทั่ว JREs เป็นการดีกว่าที่จะใช้ API เว็บเซอร์อย่างเป็นทางการในjavax.xml.wsเพื่อบูตเซิร์ฟเวอร์ HTTP ที่น้อยที่สุด ...

import java.io._
import javax.xml.ws._
import javax.xml.ws.http._
import javax.xml.transform._
import javax.xml.transform.stream._

@WebServiceProvider
@ServiceMode(value=Service.Mode.PAYLOAD) 
class P extends Provider[Source] {
  def invoke(source: Source) = new StreamSource( new StringReader("<p>Hello There!</p>"));
}

val address = "http://127.0.0.1:8080/"
Endpoint.create(HTTPBinding.HTTP_BINDING, new P()).publish(address)

println("Service running at "+address)
println("Type [CTRL]+[C] to quit!")

Thread.sleep(Long.MaxValue)

แก้ไข: ใช้งานได้จริง! โค้ดด้านบนดูเหมือนว่า Groovy หรืออะไรบางอย่าง นี่คือการแปลภาษา Java ที่ฉันทดสอบ:

import java.io.*;
import javax.xml.ws.*;
import javax.xml.ws.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

@WebServiceProvider
@ServiceMode(value = Service.Mode.PAYLOAD)
public class Server implements Provider<Source> {

    public Source invoke(Source request) {
        return  new StreamSource(new StringReader("<p>Hello There!</p>"));
    }

    public static void main(String[] args) throws InterruptedException {

        String address = "http://127.0.0.1:8080/";
        Endpoint.create(HTTPBinding.HTTP_BINDING, new Server()).publish(address);

        System.out.println("Service running at " + address);
        System.out.println("Type [CTRL]+[C] to quit!");

        Thread.sleep(Long.MAX_VALUE);
    }
}

1
+1 สำหรับการพกพา น่าเสียดายที่คุณไม่สามารถตั้งค่าประเภทเนื้อหาตอบกลับได้เหมือนtext/xmlเดิม
icza

1
ฉันคิดว่าคุณสามารถใช้ <code> คลาสเซิร์ฟเวอร์ใช้ผู้ให้บริการ <DataSource> {</code> ... จากนั้นระบุประเภทเนื้อหาภายในวิธี <code> getContentType () </code> ของ DataSource นอกจากนี้คุณยังสามารถแทรก WebServiceContext: <code> @Resource WebServiceContext ctx; </code> เพื่อตั้งค่าส่วนหัวอื่น ๆ และอ่านพารามิเตอร์คำขอ น่าเสียดายที่การตั้งค่าประเภทเนื้อหาผ่าน WebServiceContext ไม่ทำงาน
gruenewa

4
คุณช่วยอธิบายได้ไหมว่าทำไม com.sun.net.HttpServer จึงไม่สามารถพกพาข้าม JRE ได้
javabeangrinder

3
ไม่ฉันไม่คิดอย่างนั้น มันจะไม่ทำงานกับการปรับใช้ IBMs Java และอื่น ๆ และแม้ว่าจะใช้งานได้ในขณะนี้ API ภายในก็สามารถเปลี่ยนได้ ทำไมไม่ใช้ API อย่างเป็นทางการ?
gruenewa

1
ลิงค์นี้: docs.oracle.com/javase/9/docs/api/java.xml.ws-summary.htmlกล่าวว่าโมดูล java.xml.ws เลิกใช้แล้วตั้งแต่ Java 9
Erel Segal-Halevi

23

ฉันชอบคำถามนี้เพราะเป็นพื้นที่ที่มีนวัตกรรมอย่างต่อเนื่องและจำเป็นต้องมีเซิร์ฟเวอร์ไฟโดยเฉพาะเมื่อพูดถึงเซิร์ฟเวอร์ฝังตัวในอุปกรณ์ขนาดเล็ก ฉันคิดว่าคำตอบแบ่งออกเป็นสองกลุ่มกว้าง ๆ

  1. Thin-server : เนื้อหาแบบสแตติกเซิร์ฟเวอร์ขึ้นกับการประมวลผลน้อยที่สุดการประมวลผลบริบทหรือเซสชัน
  2. เซิร์ฟเวอร์ขนาดเล็ก : อย่างเห็นได้ชัด a มีคุณสมบัติคล้ายเซิร์ฟเวอร์ httpD จำนวนมากที่มีขนาดเล็กเท่าที่คุณสามารถทำได้

ในขณะที่ฉันอาจพิจารณาห้องสมุด HTTP เช่น: Jetty , Apache Http Components , Nettyและอื่น ๆ ให้เป็นเหมือนเครื่องมือประมวลผล HTTP แบบดิบ การติดฉลากนั้นเป็นเรื่องส่วนตัวมากและขึ้นอยู่กับประเภทของสิ่งที่คุณได้รับเพื่อส่งมอบให้กับไซต์เล็ก ๆ ฉันแยกความแตกต่างนี้ด้วยจิตวิญญาณของคำถามโดยเฉพาะคำพูดเกี่ยวกับ ...

  • "... โดยไม่ต้องเขียนโค้ดเพื่อแยกวิเคราะห์คำขอ HTTP และจัดรูปแบบการตอบกลับ HTTP ด้วยตนเอง ... "

เครื่องมือดิบเหล่านี้ช่วยให้คุณทำเช่นนั้น (ตามที่อธิบายไว้ในคำตอบอื่น ๆ ) พวกเขาไม่ได้ยืมตัวเองไปสู่รูปแบบที่พร้อมในการสร้างไฟฝังตัวหรือเซิร์ฟเวอร์ขนาดเล็ก เซิร์ฟเวอร์ขนาดเล็กเป็นสิ่งที่ให้ฟังก์ชั่นที่คล้ายกันกับเว็บเซิร์ฟเวอร์แบบเต็มฟังก์ชั่น (เช่นพูด, Tomcat ) โดยปราศจากเสียงระฆังและเสียงดัง, เสียงเบา, ประสิทธิภาพที่ดี 99% ของเวลา เซิร์ฟเวอร์แบบ thin ดูเหมือนจะใกล้เคียงกับการใช้ถ้อยคำดั้งเดิมเพียงเล็กน้อยมากกว่า raw อาจมีฟังก์ชั่นชุดย่อยที่ จำกัด เพียงพอที่จะทำให้คุณดูดี 90% ของเวลา ความคิดเกี่ยวกับวัตถุดิบของฉันจะทำให้ฉันดูดี 75% - 89% ของเวลาโดยไม่มีการออกแบบและการเข้ารหัสพิเศษ ฉันคิดว่าถ้า / ถึงระดับของไฟล์ WAR เราได้ปล่อย "เล็ก" สำหรับเซิร์ฟเวอร์ bonsi ที่ดูเหมือนว่าทุกอย่างที่เซิร์ฟเวอร์ขนาดใหญ่มีขนาดเล็กลง

ตัวเลือกเซิร์ฟเวอร์แบบบาง

ตัวเลือกเซิร์ฟเวอร์ขนาดเล็ก:

  • Spark Java ... เป็นไปได้ด้วยตัวช่วยสร้างมากมายเช่นตัวกรองเทมเพลต ฯลฯ
  • MadVoc ... มุ่งหวังที่จะเป็นบอนไซและอาจเป็นเช่นนั้น ;-)

ในสิ่งอื่น ๆ ที่ต้องพิจารณาฉันจะรวมการรับรองความถูกต้องการตรวจสอบความเป็นสากลโดยใช้บางอย่างเช่นFreeMakerหรือเครื่องมือเทมเพลตอื่น ๆ เพื่อแสดงผลหน้าเว็บ มิฉะนั้นการจัดการการแก้ไข HTML และการตั้งค่าพารามิเตอร์น่าจะทำให้การทำงานกับ HTTP ดูเหมือนว่า noughts-n-crosses ทุกอย่างขึ้นอยู่กับความยืดหยุ่นที่คุณต้องการ หากเป็นเครื่อง FAX ที่ใช้เมนูเป็นเรื่องง่าย ยิ่งกรอบโต้ตอบของคุณ ' หนา ' มากขึ้นเท่าไหร่ เป็นคำถามที่ดีโชคดี!


21

มีลักษณะที่ "ท่าเทียบเรือ" เว็บเซิร์ฟเวอร์ที่ท่าเทียบเรือ สุดยอดซอฟต์แวร์โอเพนซอร์ซที่ดูเหมือนจะตอบสนองทุกความต้องการของคุณ

หากคุณยืนยันในการกลิ้งของคุณเองดูที่คลาส "httpMessage"


ฉันคิดว่าท่าเทียบเรือ API ขึ้นอยู่กับ servlet
โต้แย้งได้

4
@Iputable: ไม่ Jetty เป็นเว็บเซิร์ฟเวอร์แบบแยกส่วนสูงซึ่งมี servlet container เป็นหนึ่งในโมดูลเสริม
Lawrence Dol

"เป็นแอนะล็อกสำหรับการทำงานของเซิร์ฟเวอร์" - ใช่ "servlet" API ภาชนะ servlet เรียกชั้นเรียนของคุณหลังจากที่มันได้แยกวิเคราะห์ส่วนหัว, คุกกี้ ฯลฯ
เจมส์เดอร์สัน

1
เพียงบันทึก - ท่าเทียบเรือมาพร้อมกับการใช้งานของตัวเองของ Servlet API และทำงานได้ดีกับ Java SE
James Anderson

4
ท่าเทียบเรือมีขนาดใหญ่เกินไปและมีช่วงการเรียนรู้มากเกินไปก่อนที่การใช้งานจริงจะเป็นไปได้
ThreaT

18

กาลครั้งหนึ่งฉันกำลังมองหาสิ่งที่คล้ายกัน - เซิร์ฟเวอร์ HTTP น้ำหนักเบา แต่ทำงานได้อย่างสมบูรณ์ที่ฉันสามารถฝังและปรับแต่งได้อย่างง่ายดาย ฉันพบวิธีแก้ปัญหาที่เป็นไปได้สองประเภท:

  • เซิร์ฟเวอร์เต็มรูปแบบที่ไม่ได้มีน้ำหนักเบาหรือเรียบง่าย (สำหรับคำจำกัดความที่เบามาก)
  • เซิร์ฟเวอร์น้ำหนักเบาอย่างแท้จริงที่ไม่ได้เป็นเซิร์ฟเวอร์ HTTP แต่ตัวอย่าง ServerSocket ที่น่ายกย่องซึ่งไม่สอดคล้องกับ RFC จากระยะไกลและไม่สนับสนุนฟังก์ชันพื้นฐานที่จำเป็นโดยทั่วไป

ดังนั้น ... ฉันออกไปเขียนJLHTTP - เดอะ Java น้ำหนักเบา HTTP Server

คุณสามารถฝังไว้ในโครงการใด ๆ เป็นไฟล์ต้นฉบับ (ถ้าค่อนข้างยาว) หรือเป็นไฟล์ ~ 50K jar (~ 35K ปล้น) โดยไม่มีการขึ้นต่อกัน มันมุ่งมั่นที่จะเป็นไปตาม RFC และมีเอกสารที่ครอบคลุมและคุณสมบัติที่มีประโยชน์มากมายในขณะที่การขยายตัวให้น้อยที่สุด

คุณสมบัติรวมถึง: โฮสต์เสมือนการให้บริการไฟล์จากดิสก์การแม็พชนิด mime ผ่านไฟล์ mime.types มาตรฐานการสร้างดัชนีไดเรกทอรีไฟล์ต้อนรับการสนับสนุนวิธี HTTP ทั้งหมด ETags ที่มีเงื่อนไขและการสนับสนุนส่วนหัว if- * การเข้ารหัสการถ่ายโอน chunked, gzip / deflate การบีบอัด HTTPS พื้นฐาน (จัดทำโดย JVM) เนื้อหาบางส่วน (ความต่อเนื่องในการดาวน์โหลด) การจัดการหลายส่วน / ฟอร์มข้อมูลสำหรับการอัปโหลดไฟล์ตัวจัดการบริบทหลายตัวผ่าน API หรือคำอธิบายประกอบพารามิเตอร์การแยกวิเคราะห์ (สตริงแบบสอบถามหรือ x-www-form-urlencoded ร่างกาย) ฯลฯ

ฉันหวังว่าคนอื่นจะเห็นว่ามีประโยชน์ :-)


วิธีการหลักเป็นตัวอย่างที่ดีของการใช้งานขั้นพื้นฐานและคำถามที่พบบ่อยมีรายละเอียดมากมาย หากคุณมีข้อเสนอแนะสำหรับการปรับปรุงเอกสารที่มีอยู่โปรดติดต่อฉันโดยตรง!
amichair



8

เป็นไปได้ที่จะสร้าง httpserver ที่ให้การสนับสนุนขั้นพื้นฐานสำหรับ J2EE เซิร์ฟเล็ตที่มีเพียง JDK และ servlet api ในโค้ดไม่กี่บรรทัด

ฉันพบสิ่งนี้มีประโยชน์มากสำหรับการทดสอบหน่วย servlet เพราะมันเริ่มเร็วกว่าคอนเทนเนอร์น้ำหนักเบาอื่น ๆ (เราใช้ท่าเทียบเรือเพื่อการผลิต)

httpservers ที่มีน้ำหนักเบามากส่วนใหญ่ไม่ให้การสนับสนุน servlets แต่เราต้องการพวกเขาดังนั้นฉันคิดว่าฉันจะแบ่งปัน

ตัวอย่างด้านล่างให้การสนับสนุน servlet พื้นฐานหรือส่งและ UnsupportedOperationException สำหรับสิ่งที่ยังไม่ได้ใช้งาน มันใช้ com.sun.net.httpserver.HttpServer สำหรับการสนับสนุน http พื้นฐาน

import java.io.*;
import java.lang.reflect.*;
import java.net.InetSocketAddress;
import java.util.*;

import javax.servlet.*;
import javax.servlet.http.*;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

@SuppressWarnings("deprecation")
public class VerySimpleServletHttpServer {
    HttpServer server;
    private String contextPath;
    private HttpHandler httpHandler;

    public VerySimpleServletHttpServer(String contextPath, HttpServlet servlet) {
        this.contextPath = contextPath;
        httpHandler = new HttpHandlerWithServletSupport(servlet);
    }

    public void start(int port) throws IOException {
        InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
        server = HttpServer.create(inetSocketAddress, 0);
        server.createContext(contextPath, httpHandler);
        server.setExecutor(null);
        server.start();
    }

    public void stop(int secondsDelay) {
        server.stop(secondsDelay);
    }

    public int getServerPort() {
        return server.getAddress().getPort();
    }

}

final class HttpHandlerWithServletSupport implements HttpHandler {

    private HttpServlet servlet;

    private final class RequestWrapper extends HttpServletRequestWrapper {
        private final HttpExchange ex;
        private final Map<String, String[]> postData;
        private final ServletInputStream is;
        private final Map<String, Object> attributes = new HashMap<>();

        private RequestWrapper(HttpServletRequest request, HttpExchange ex, Map<String, String[]> postData, ServletInputStream is) {
            super(request);
            this.ex = ex;
            this.postData = postData;
            this.is = is;
        }

        @Override
        public String getHeader(String name) {
            return ex.getRequestHeaders().getFirst(name);
        }

        @Override
        public Enumeration<String> getHeaders(String name) {
            return new Vector<String>(ex.getRequestHeaders().get(name)).elements();
        }

        @Override
        public Enumeration<String> getHeaderNames() {
            return new Vector<String>(ex.getRequestHeaders().keySet()).elements();
        }

        @Override
        public Object getAttribute(String name) {
            return attributes.get(name);
        }

        @Override
        public void setAttribute(String name, Object o) {
            this.attributes.put(name, o);
        }

        @Override
        public Enumeration<String> getAttributeNames() {
            return new Vector<String>(attributes.keySet()).elements();
        }

        @Override
        public String getMethod() {
            return ex.getRequestMethod();
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return is;
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public String getPathInfo() {
            return ex.getRequestURI().getPath();
        }

        @Override
        public String getParameter(String name) {
            String[] arr = postData.get(name);
            return arr != null ? (arr.length > 1 ? Arrays.toString(arr) : arr[0]) : null;
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return postData;
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return new Vector<String>(postData.keySet()).elements();
        }
    }

    private final class ResponseWrapper extends HttpServletResponseWrapper {
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        final ServletOutputStream servletOutputStream = new ServletOutputStream() {

            @Override
            public void write(int b) throws IOException {
                outputStream.write(b);
            }
        };

        private final HttpExchange ex;
        private final PrintWriter printWriter;
        private int status = HttpServletResponse.SC_OK;

        private ResponseWrapper(HttpServletResponse response, HttpExchange ex) {
            super(response);
            this.ex = ex;
            printWriter = new PrintWriter(servletOutputStream);
        }

        @Override
        public void setContentType(String type) {
            ex.getResponseHeaders().add("Content-Type", type);
        }

        @Override
        public void setHeader(String name, String value) {
            ex.getResponseHeaders().add(name, value);
        }

        @Override
        public javax.servlet.ServletOutputStream getOutputStream() throws IOException {
            return servletOutputStream;
        }

        @Override
        public void setContentLength(int len) {
            ex.getResponseHeaders().add("Content-Length", len + "");
        }

        @Override
        public void setStatus(int status) {
            this.status = status;
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            this.status = sc;
            if (msg != null) {
                printWriter.write(msg);
            }
        }

        @Override
        public void sendError(int sc) throws IOException {
            sendError(sc, null);
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            return printWriter;
        }

        public void complete() throws IOException {
            try {
                printWriter.flush();
                ex.sendResponseHeaders(status, outputStream.size());
                if (outputStream.size() > 0) {
                    ex.getResponseBody().write(outputStream.toByteArray());
                }
                ex.getResponseBody().flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                ex.close();
            }
        }
    }

    public HttpHandlerWithServletSupport(HttpServlet servlet) {
        this.servlet = servlet;
    }

    @SuppressWarnings("deprecation")
    @Override
    public void handle(final HttpExchange ex) throws IOException {
        byte[] inBytes = getBytes(ex.getRequestBody());
        ex.getRequestBody().close();
        final ByteArrayInputStream newInput = new ByteArrayInputStream(inBytes);
        final ServletInputStream is = new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return newInput.read();
            }
        };

        Map<String, String[]> parsePostData = new HashMap<>();

        try {
            parsePostData.putAll(HttpUtils.parseQueryString(ex.getRequestURI().getQuery()));

            // check if any postdata to parse
            parsePostData.putAll(HttpUtils.parsePostData(inBytes.length, is));
        } catch (IllegalArgumentException e) {
            // no postData - just reset inputstream
            newInput.reset();
        }
        final Map<String, String[]> postData = parsePostData;

        RequestWrapper req = new RequestWrapper(createUnimplementAdapter(HttpServletRequest.class), ex, postData, is);

        ResponseWrapper resp = new ResponseWrapper(createUnimplementAdapter(HttpServletResponse.class), ex);

        try {
            servlet.service(req, resp);
            resp.complete();
        } catch (ServletException e) {
            throw new IOException(e);
        }
    }

    private static byte[] getBytes(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        while (true) {
            int r = in.read(buffer);
            if (r == -1)
                break;
            out.write(buffer, 0, r);
        }
        return out.toByteArray();
    }

    @SuppressWarnings("unchecked")
    private static <T> T createUnimplementAdapter(Class<T> httpServletApi) {
        class UnimplementedHandler implements InvocationHandler {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                throw new UnsupportedOperationException("Not implemented: " + method + ", args=" + Arrays.toString(args));
            }
        }

        return (T) Proxy.newProxyInstance(UnimplementedHandler.class.getClassLoader(),
                new Class<?>[] { httpServletApi },
                new UnimplementedHandler());
    }
}

นี่ขาดวิธีการบางอย่างสำหรับ ServletOutputStream และ ServletInputStream
HomeIsWhereThePcIs

servlet api เวอร์ชันใหม่กว่าขึ้นไปมีขนาด 3.0 และต่ำกว่า เพียงเพิ่ม methdos ที่หายไปตามที่ต้องการลงในตัวอย่าง
f.carlsen

6

ฉันสามารถแนะนำให้ดูเป็นเรื่องง่ายโดยเฉพาะอย่างยิ่งหากคุณไม่ต้องการความสามารถของ Servlet แต่เพียงเข้าถึงวัตถุคำขอ / reponse ถ้าคุณต้องการ REST คุณสามารถใส่ Jersey ไว้ด้านบนของมันถ้าคุณต้องการที่จะเอาท์พุท HTML หรือคล้ายกันนั่นคือมี Freemarker ฉันรักสิ่งที่คุณสามารถทำได้กับชุดค่าผสมนี้และมี API ค่อนข้างน้อยที่จะเรียนรู้


+1 ฉันชอบความคิดเบื้องหลัง Simple อย่างไรก็ตามมีปัญหาเกิดขึ้นเมื่อพยายามใช้ HTTPS เพราะ Mamba จะนำคุณสมบัติ "ฝังตัว" ออกจาก Simple
ภัยคุกคามที่

6

รหัสนี้จะดีกว่าของเราคุณต้องการเพียงที่จะเพิ่ม 2 libs: javax.servelet.jarและorg.mortbay.jetty.jar org.mortbay.jetty.jar

Class Jetty:

package jetty;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.mortbay.http.SocketListener;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHttpContext;

public class Jetty {

    public static void main(String[] args) {
        try {
            Server server = new Server();
            SocketListener listener = new SocketListener();      

            System.out.println("Max Thread :" + listener.getMaxThreads() + " Min Thread :" + listener.getMinThreads());

            listener.setHost("localhost");
            listener.setPort(8070);
            listener.setMinThreads(5);
            listener.setMaxThreads(250);
            server.addListener(listener);            

            ServletHttpContext context = (ServletHttpContext) server.getContext("/");
            context.addServlet("/MO", "jetty.HelloWorldServlet");

            server.start();
            server.join();

        /*//We will create our server running at http://localhost:8070
        Server server = new Server();
        server.addListener(":8070");

        //We will deploy our servlet to the server at the path '/'
        //it will be available at http://localhost:8070
        ServletHttpContext context = (ServletHttpContext) server.getContext("/");
        context.addServlet("/MO", "jetty.HelloWorldServlet");

        server.start();
        */

        } catch (Exception ex) {
            Logger.getLogger(Jetty.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
} 

ระดับ servlet:

package jetty;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloWorldServlet extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException
    {
        String appid = httpServletRequest.getParameter("appid");
        String conta = httpServletRequest.getParameter("conta");

        System.out.println("Appid : "+appid);
        System.out.println("Conta : "+conta);

        httpServletResponse.setContentType("text/plain");
        PrintWriter out = httpServletResponse.getWriter();
        out.println("Hello World!");
        out.close();
    }
}

2
คำถามถามหาทางออก Java SE อย่างหมดจด คุณจะพบท่าเทียบเรือที่ใช้ Java EE API
Sridhar

ท่าเทียบเรือทำงานอย่างสมบูรณ์แบบดีโดยใช้ Java SE มาตรฐานและดังนั้นจึงเหมาะกับความต้องการ มันดำเนินการในส่วนของ Java EE API ก็ไม่ได้ต้องการมัน มีความแตกต่าง
David Tonhofer

1
สิ่งนี้ไม่มีคุณสมบัติ "โดยใช้เพียง Java SE API" *.Servlet.jarและ*.jetty.jarเห็นได้ชัดว่าไม่ใช่ส่วนหนึ่งของ Java SE
icza

ฉันจำเป็นต้องตั้งท่าเทียบเรือหรือไม่ หรือฉันจะแยกแยะทั้งสองขวดและเรียกใช้ไฟล์นี้ได้ไหม?
Paul Preibisch


4

ทั้งหมดข้างต้นตอบรายละเอียดเกี่ยวกับตัวจัดการการร้องขอเธรดหลักเดียว

การตั้งค่า:

 server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());

อนุญาตการร้องขอหลายการแสดงผ่านหลายเธรดโดยใช้บริการตัวจัดการ

ดังนั้นรหัสสิ้นสุดจะเป็นดังนี้:

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
public class App {
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/test", new MyHandler());
        //Thread control is given to executor service.
        server.setExecutor(java.util.concurrent.Executors.newCachedThreadPool());
        server.start();
    }
    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange t) throws IOException {
            String response = "This is the response";
            long threadId = Thread.currentThread().getId();
            System.out.println("I am thread " + threadId );
            response = response + "Thread Id = "+threadId;
            t.sendResponseHeaders(200, response.length());
            OutputStream os = t.getResponseBody();
            os.write(response.getBytes());
            os.close();
        }
    }
}

3

ชำระเงินง่ายๆ มันเป็นเซิร์ฟเวอร์ที่ฝังตัวได้ค่อนข้างง่ายพร้อมการสนับสนุนในตัวสำหรับการใช้งานที่หลากหลาย ฉันชอบแบบจำลองเกลียวของมันมาก ..

! ที่น่าตื่นตาตื่นใจ



2

โครงการApache Commons HttpCore เป็นอย่างไร?

จากเว็บไซต์: ... HttpCore Goals

  • การใช้งานการขนส่ง HTTP พื้นฐานที่สุด
  • สมดุลระหว่างประสิทธิภาพที่ดีและความชัดเจนและความหมายของ API
  • รอยเท้าหน่วยความจำขนาดเล็ก (คาดการณ์ได้)
  • ไลบรารีในตัวเอง (ไม่มีการขึ้นต่อกันที่อื่นนอกจาก JRE)

นั่นอาจเป็นระดับต่ำเกินไป อย่างน้อยหนึ่งควรมีจุดมุ่งหมายเพื่อแก้ปัญหาที่เรียกรหัสของคุณในระดับ servlet API เว้นแต่จะต้องการจัดการกับแนวคิดทั้งหมดเช่นการแยกการเข้ารหัส ฯลฯ ด้วยตัวเอง มันสามารถสนุกได้
David Tonhofer

2

ลองใช้https://github.com/devashish234073/Java-Socket-Http-Server/blob/master/README.md

API นี้สร้างเซิร์ฟเวอร์ HTTP โดยใช้ซ็อกเก็ต

  1. ได้รับคำขอจากเบราว์เซอร์เป็นข้อความ
  2. แยกวิเคราะห์เพื่อดึงข้อมูล URL วิธีการคุณลักษณะ ฯลฯ
  3. สร้างการตอบสนองแบบไดนามิกโดยใช้การแม็พ URL ที่กำหนดไว้
  4. ส่งการตอบกลับไปยังเบราว์เซอร์

ตัวอย่างเช่นต่อไปนี้เป็นวิธีที่ตัวสร้างในResponse.javaคลาสแปลงการตอบกลับแบบ raw เป็นการตอบสนองแบบ http:

public Response(String resp){
    Date date = new Date();
    String start = "HTTP/1.1 200 OK\r\n";
    String header = "Date: "+date.toString()+"\r\n";
    header+= "Content-Type: text/html\r\n";
    header+= "Content-length: "+resp.length()+"\r\n";
    header+="\r\n";
    this.resp=start+header+resp;
}

1

คุณสามารถเขียนJetty ที่ฝังตัวได้ง่ายเซิร์ฟเวอร์ Java

Embedded Jetty หมายถึงเซิร์ฟเวอร์ (Jetty) จัดส่งพร้อมกับแอปพลิเคชันซึ่งต่างจากการปรับใช้แอปพลิเคชันบนเซิร์ฟเวอร์ Jetty ภายนอก

ดังนั้นหากใช้วิธีที่ไม่ได้ฝัง webapp ของคุณจะถูกสร้างขึ้นในไฟล์ WAR ซึ่งปรับใช้กับเซิร์ฟเวอร์ภายนอก ( Tomcat) / Jetty / etc) ใน Jetty ที่ฝังตัวคุณเขียน webapp และยกตัวอย่างเซิร์ฟเวอร์ jetty ในฐานรหัสเดียวกัน

ตัวอย่างสำหรับเซิร์ฟเวอร์ Jetty Java ในตัวที่คุณสามารถคอมไพล์โคลนและใช้งานได้: https://github.com/stas-slu/embedded-jetty-java-server-example

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.