diff --git a/src/zutil/net/http/HttpHeader.java b/src/zutil/net/http/HttpHeader.java index c062684..98acaea 100755 --- a/src/zutil/net/http/HttpHeader.java +++ b/src/zutil/net/http/HttpHeader.java @@ -31,6 +31,7 @@ import java.util.Iterator; public class HttpHeader { // HTTP info + private boolean request; private String type; private String url; private HashMap urlAttributes; @@ -49,6 +50,18 @@ public class HttpHeader { } + /** + * @return true if this header represents a server response + */ + public boolean isResponse(){ + return !request; + } + /** + * @return true if this header represents a client request + */ + public boolean isRequest(){ + return request; + } /** * @return the HTTP message type( ex. GET,POST...) */ @@ -117,13 +130,7 @@ public class HttpHeader { } - protected HashMap getCookieMap(){ - return cookies; - } - protected HashMap getUrlAttributeMap(){ - return urlAttributes; - } - + protected void setIsRequest(boolean request) { this.request = request; } protected void setRequestType(String type){ this.type = type.trim(); } @@ -146,6 +153,13 @@ public class HttpHeader { headers.put(key.trim(), value.trim()); } + protected HashMap getCookieMap(){ + return cookies; + } + protected HashMap getUrlAttributeMap(){ + return urlAttributes; + } + public String toString(){ diff --git a/src/zutil/net/http/HttpHeaderParser.java b/src/zutil/net/http/HttpHeaderParser.java index 82c8a19..5fc4a1c 100755 --- a/src/zutil/net/http/HttpHeaderParser.java +++ b/src/zutil/net/http/HttpHeaderParser.java @@ -87,11 +87,13 @@ public class HttpHeaderParser { protected static void parseStatusLine(HttpHeader header, String line){ // Server Response if( line.startsWith("HTTP/") ){ + header.setIsRequest(false); header.setHTTPVersion( Float.parseFloat( line.substring( 5 , 8))); header.setHTTPCode( Integer.parseInt( line.substring( 9, 12 ))); } // Client Request else if(line.contains("HTTP/")){ + header.setIsRequest(true); header.setRequestType( line.substring(0, line.indexOf(" "))); header.setHTTPVersion( Float.parseFloat( line.substring(line.lastIndexOf("HTTP/")+5 , line.length()).trim())); line = (line.substring(header.getRequestType().length()+1, line.lastIndexOf("HTTP/"))); diff --git a/src/zutil/net/http/HttpPrintStream.java b/src/zutil/net/http/HttpPrintStream.java index 74dec8b..65b1f16 100755 --- a/src/zutil/net/http/HttpPrintStream.java +++ b/src/zutil/net/http/HttpPrintStream.java @@ -208,9 +208,9 @@ public class HttpPrintStream extends OutputStream{ else{ if(res_status_code != null){ if( message_type==HttpMessageType.REQUEST ) - out.print(req_type + " " + req_url + " HTTP/1.0"); + out.print(req_type + " " + req_url + " HTTP/1.1"); else - out.print("HTTP/1.0 " + res_status_code + " " + getStatusString(res_status_code)); + out.print("HTTP/1.1 " + res_status_code + " " + getStatusString(res_status_code)); out.println(); res_status_code = null; req_type = null; diff --git a/src/zutil/net/ssdp/SSDPClient.java b/src/zutil/net/ssdp/SSDPClient.java index 295574b..7516845 100755 --- a/src/zutil/net/ssdp/SSDPClient.java +++ b/src/zutil/net/ssdp/SSDPClient.java @@ -24,7 +24,6 @@ package zutil.net.ssdp; -import zutil.io.StringOutputStream; import zutil.log.LogUtil; import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeaderParser; @@ -32,6 +31,7 @@ import zutil.net.http.HttpPrintStream; import zutil.net.threaded.ThreadedUDPNetwork; import zutil.net.threaded.ThreadedUDPNetworkThread; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; @@ -40,6 +40,9 @@ import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; +import static zutil.net.ssdp.SSDPServer.SSDP_MULTICAST_ADDR; +import static zutil.net.ssdp.SSDPServer.SSDP_PORT; + /** * An SSDP client class that will request * service information. @@ -48,6 +51,7 @@ import java.util.logging.Logger; */ public class SSDPClient extends ThreadedUDPNetwork implements ThreadedUDPNetworkThread{ private static final Logger logger = LogUtil.getLogger(); + public static final String USER_AGENT = "Zutil SSDP Client"; /** Mapping of search targets and list of associated services **/ private HashMap> services_st; /** Map of all unique services received **/ @@ -63,6 +67,7 @@ public class SSDPClient extends ThreadedUDPNetwork implements ThreadedUDPNetwork * @throws IOException */ public SSDPClient() throws IOException{ + super( SSDP_MULTICAST_ADDR, SSDP_PORT ); super.setThread(this); services_st = new HashMap<>(); @@ -88,14 +93,15 @@ public class SSDPClient extends ThreadedUDPNetwork implements ThreadedUDPNetwork public void requestService(String searchTarget, HashMap headers){ try { // Generate an SSDP discover message - StringOutputStream msg = new StringOutputStream(); - HttpPrintStream http = new HttpPrintStream( msg, HttpPrintStream.HttpMessageType.REQUEST ); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + HttpPrintStream http = new HttpPrintStream( buffer, HttpPrintStream.HttpMessageType.REQUEST ); http.setRequestType("M-SEARCH"); http.setRequestURL("*"); - http.setHeader("Host", SSDPServer.SSDP_MULTICAST_ADDR +":"+ SSDPServer.SSDP_PORT ); + http.setHeader("Host", SSDP_MULTICAST_ADDR +":"+ SSDP_PORT ); http.setHeader("ST", searchTarget ); http.setHeader("Man", "\"ssdp:discover\"" ); http.setHeader("MX", "3" ); + http.setHeader("USER-AGENT", USER_AGENT ); if(headers != null) { for (String key : headers.keySet()) { http.setHeader(key, headers.get(key)); @@ -104,11 +110,12 @@ public class SSDPClient extends ThreadedUDPNetwork implements ThreadedUDPNetwork logger.log(Level.FINEST, "Sending Multicast: "+ http); http.flush(); - byte[] data = msg.toString().getBytes(); + byte[] data = buffer.toByteArray(); + //System.out.println(new String(data)+"****************"); DatagramPacket packet = new DatagramPacket( data, data.length, - InetAddress.getByName( SSDPServer.SSDP_MULTICAST_ADDR ), - SSDPServer.SSDP_PORT ); + InetAddress.getByName( SSDP_MULTICAST_ADDR ), + SSDP_PORT ); super.send( packet ); http.close(); } catch (Exception e) { @@ -193,38 +200,57 @@ public class SSDPClient extends ThreadedUDPNetwork implements ThreadedUDPNetwork String msg = new String(packet.getData(), packet.getOffset(), packet.getLength()); HttpHeaderParser headerParser = new HttpHeaderParser(msg); HttpHeader header = headerParser.read(); - logger.log(Level.FINEST, "Received(from: " + packet.getAddress() + "): " + header); String usn = header.getHeader("USN"); String st = header.getHeader("ST"); - boolean newService = false; - StandardSSDPInfo service; + StandardSSDPInfo service = null; // Get existing service if (services_usn.containsKey(usn)) { service = services_usn.get(usn); } - // Add new service + + // Remove service + if ("NOTIFY".equals(header.getRequestType()) && "ssdp:byebye".equalsIgnoreCase(header.getHeader("NTS"))){ + logger.log(Level.FINER, "Received NOTIFY:byebye (from: " + packet.getAddress() + "): " + header); + if (service != null) { + services_usn.remove(usn); + if (services_st.containsKey(st)) + services_st.get(st).remove(service); + if (listener != null) + listener.serviceLost(service); + } + } + // Existing or new service update + else if (header.isResponse() || "NOTIFY".equals(header.getRequestType())) { + logger.log(Level.FINER, "Received service update (from: " + packet.getAddress() + "): " + header); + boolean newService = false; + + // Add new service + if (service == null){ + newService = true; + service = new StandardSSDPInfo(); + services_usn.put(usn, service); + if (!services_st.containsKey(st)) + services_st.put(st, new LinkedList()); + services_st.get(header.getHeader("ST")).add(service); + } + + service.setLocation(header.getHeader("LOCATION")); + service.setST(st); + service.setUSN(usn); + service.setInetAddress(packet.getAddress()); + if (header.getHeader("Cache-Control") != null) { + service.setExpirationTime( + System.currentTimeMillis() + 1000 * getCacheTime(header.getHeader("Cache-Control"))); + } + service.readHeaders(header); + + if (listener != null && newService) + listener.serviceDiscovered(service); + } else { - newService = true; - service = new StandardSSDPInfo(); - services_usn.put(usn, service); - if (!services_st.containsKey(st)) - services_st.put(st, new LinkedList()); - services_st.get(header.getHeader("ST")).add(service); + logger.log(Level.FINEST, "Ignored (from: " + packet.getAddress() + "): " + header); } - - service.setLocation(header.getHeader("LOCATION")); - service.setST(st); - service.setUSN(usn); - service.setInetAddress(packet.getAddress()); - if (header.getHeader("Cache-Control") != null) { - service.setExpirationTime( - System.currentTimeMillis() + 1000 * getCacheTime(header.getHeader("Cache-Control"))); - } - service.readHeaders(header); - - if (listener != null && newService) - listener.newService(service); } catch (IOException e){ logger.log(Level.SEVERE, null, e); } @@ -243,6 +269,14 @@ public class SSDPClient extends ThreadedUDPNetwork implements ThreadedUDPNetwork } public interface SSDPServiceListener{ - public void newService(StandardSSDPInfo service); - } + /** + * Is called when a new service is discovered. Will only be called once per service. + */ + void serviceDiscovered(StandardSSDPInfo service); + + /** + * Is called when a service goes down and is not available anymore. + */ + void serviceLost(StandardSSDPInfo service); + } } diff --git a/src/zutil/net/ssdp/SSDPServer.java b/src/zutil/net/ssdp/SSDPServer.java index a5471d2..b392407 100755 --- a/src/zutil/net/ssdp/SSDPServer.java +++ b/src/zutil/net/ssdp/SSDPServer.java @@ -33,6 +33,7 @@ import zutil.net.http.HttpPrintStream; import zutil.net.threaded.ThreadedUDPNetwork; import zutil.net.threaded.ThreadedUDPNetworkThread; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; @@ -68,9 +69,8 @@ import java.util.logging.Logger; */ public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetworkThread{ private static final Logger logger = LogUtil.getLogger(); - public static final String SERVER_INFO = "Zutil SSDP Java Server"; + public static final String SERVER_INFO = "Zutil SSDP Server"; public static final int DEFAULT_CACHE_TIME = 60*30; // 30 min - public static final int BUFFER_SIZE = 512; public static final String SSDP_MULTICAST_ADDR = "239.255.255.250"; public static final int SSDP_PORT = 1900; @@ -186,8 +186,8 @@ public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetwork logger.log(Level.FINEST, "Received Multicast(from: "+packet.getAddress()+"): "+ header); // Generate the SSDP response - StringOutputStream response = new StringOutputStream(); - HttpPrintStream http = new HttpPrintStream( response ); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + HttpPrintStream http = new HttpPrintStream( buffer ); http.setStatusCode(200); http.setHeader("Location", services.get(st).getLocation() ); http.setHeader("USN", services.get(st).getUSN() ); @@ -200,8 +200,7 @@ public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetwork logger.log(Level.FINEST, "Sending Response: "+ http); http.flush(); - String strData = response.toString(); - byte[] data = strData.getBytes(); + byte[] data = buffer.toByteArray(); packet = new DatagramPacket( data, data.length, packet.getAddress(), @@ -255,8 +254,8 @@ public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetwork try { SSDPServiceInfo service = services.get(searchTarget); // Generate the SSDP response - StringOutputStream msg = new StringOutputStream(); - HttpPrintStream http = new HttpPrintStream( msg, HttpPrintStream.HttpMessageType.REQUEST ); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + HttpPrintStream http = new HttpPrintStream( buffer, HttpPrintStream.HttpMessageType.REQUEST ); http.setRequestType("NOTIFY"); http.setRequestURL("*"); http.setHeader("Server", SERVER_INFO ); @@ -271,7 +270,7 @@ public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetwork logger.log(Level.FINEST, "Sending Notification: " + http); http.flush(); - byte[] data = msg.toString().getBytes(); + byte[] data = buffer.toByteArray(); DatagramPacket packet = new DatagramPacket( data, data.length, InetAddress.getByName( SSDP_MULTICAST_ADDR ), @@ -309,8 +308,8 @@ public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetwork public void sendByeBye(String searchTarget){ try { // Generate the SSDP response - StringOutputStream msg = new StringOutputStream(); - HttpPrintStream http = new HttpPrintStream( msg, HttpPrintStream.HttpMessageType.REQUEST ); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + HttpPrintStream http = new HttpPrintStream( buffer, HttpPrintStream.HttpMessageType.REQUEST ); http.setRequestType("NOTIFY"); http.setRequestURL("*"); http.setHeader("Server", SERVER_INFO ); @@ -321,7 +320,7 @@ public class SSDPServer extends ThreadedUDPNetwork implements ThreadedUDPNetwork logger.log(Level.FINEST, "Sending ByeBye: " + http); http.flush(); - byte[] data = msg.toString().getBytes(); + byte[] data = buffer.toByteArray(); DatagramPacket packet = new DatagramPacket( data, data.length, InetAddress.getByName( SSDP_MULTICAST_ADDR ), diff --git a/src/zutil/net/ws/soap/SOAPHttpPage.java b/src/zutil/net/ws/soap/SOAPHttpPage.java index 16bf24c..54fcb5d 100755 --- a/src/zutil/net/ws/soap/SOAPHttpPage.java +++ b/src/zutil/net/ws/soap/SOAPHttpPage.java @@ -95,7 +95,8 @@ public class SOAPHttpPage implements HttpPage{ /** * Enables session support, if enabled then a new instance * of the SOAPInterface will be created, if disabled then - * only the given object will be used as an static interface + * only the given object will be used as an static interface. + * Default is false. * * @param enabled is if session should be enabled */ diff --git a/test/zutil/net/ssdp/SSDPClientTest.java b/test/zutil/net/ssdp/SSDPClientTest.java index 15828cd..7bef245 100755 --- a/test/zutil/net/ssdp/SSDPClientTest.java +++ b/test/zutil/net/ssdp/SSDPClientTest.java @@ -41,12 +41,23 @@ public class SSDPClientTest { LogUtil.setGlobalLevel(Level.FINEST); SSDPClient ssdp = new SSDPClient(); ssdp.requestService("upnp:rootdevice"); + ssdp.requestService("urn:schemas-wifialliance-org:device:WFADevice:1"); + ssdp.requestService("urn:dial-multiscreen-org:service:dial:1"); // Chromecast + ssdp.requestService("urn:schemas-upnp-org:device:InternetGatewayDevice:1"); // Routers ssdp.start(); - for(int i=0; true ;++i){ - while( i==ssdp.getServicesCount("upnp:rootdevice") ){ try{Thread.sleep(100);}catch(Exception e){} } - System.out.println("************************" ); - System.out.println("" + ssdp.getServices("upnp:rootdevice").get(i)); - } + ssdp.setListener(new SSDPClient.SSDPServiceListener() { + @Override + public void serviceDiscovered(StandardSSDPInfo service) { + System.out.println("*********** DISCOVERY *************" ); + System.out.println("" + service); + } + + @Override + public void serviceLost(StandardSSDPInfo service) { + System.out.println("*********** LOST *************" ); + System.out.println("" + service); + } + }); } }