sobota, 14 kwietnia 2012

Notatki o Windows 8 Consumer Preview - odc. 11

Dzisiaj zacznę od skojarzeń ze studiów. Ja na trzecim roku miałem zagadnienia związane z klasycznych siecią, a na czwartym lub piątym zagadnienia z sieci mobilnych. Dlaczego akurat teraz o tym pisze? Otóż wymienię zaraz pewne pojęcia, które albo pamiętam ze studiów, albo uważam, że tam powinny się obecnie znaleźć, w związku z dokonanym postępem technicznym jaki dokonał się przez te kilka lat –;) Przy okazji niewielka dygresja, że jednak szersza wiedza ze szkoły czy studiów później wbrew powszechnym sądom się przydaje, przynajmniej jak się czyta np. taki dokument o protokołach sieciowych, to “nie czujemy się zagubieni”, “gdzieś to słyszeliśmy” i chociaż pewnie nie każdy szczegół pamiętamy, to kojarzymy o co chodzi i gdzie można poszukać więcej informacji. I w mojej opinii tak jest ze wszystkim.

Jak się można było domyśleć z dygresji poczynionej na wstępie, poruszę trochę zagadnień związanych z komunikacją sieciową.

Tradycyjnie już zacznijmy od JS. Zresztą po raz kolejny okazało się, że dokumentacja Windows 8 z perspektywy pozostałych języków jest znacznie bardziej uboga i jakby wolniej rozwijana, więc w sumie innej opcji nie ma.

Na początku moją uwagę zwróciła możliwość dość wygodnego pobierania za pomocą funkcji WinJS.xhr różnego rodzaju danych (tablica, blob, dokument, json, ms-stream, tekst). Zostało to również wprowadzone do obiektu XMLHttpRequest w przeglądarce IE 10. Funkcja WinJS.xhr pozwala także wymusić pobranie na nowo elementu strony, nawet jeśli został wcześniej umieszczony w cache’u.

W przypadku socketów dowiadujemy się o możliwości ustawienia dla nich zaawansowanych ustawień. Klasy reprezentujące poszczególne rodzaje socketów mają dedykowane dla nich klasy tzw. kontrolki. DatagramSocketControl i StreamSocketListenerControl pozwalają ustawić QoS (pojęcie ze studiów). StreamSocketControl pozwala ustawić takie parametry jak długość interwału keep-alive, używanie algorytmu Nagle (pojęcie ze studiów), rozmiar bufora czy priorytet QoS. Jeśli chcemy odczytać dodatkowe informacje, wtedy pomocne mogą być klasy *Information związane z poszczególnymi rodzajami socketów.

Komunikacja za pomocą socketów może być także zabezpieczana protokołem SSL/TLS. W metodzie otwierającej połączenie podajemy wymagany przez nas stopień zabezpieczenia (bez szyfrowania, z szyfrowaniem SSL/TLS, z obsługą SSL/TLS z możliwością braku szyfrowania) lub za pomocą odpowiedniej metody możemy dokonać “upgrade’u” połączenia z wersji niezabezpieczonej do wersji używającej SSL.

Pojęciem nadającym się obecnie do wykładania jest z pewnością websocket. Tak długo nikt nie wpadł na pomysł, by mieć prostą komunikację dwukierunkową po HTTP (obecnie) z poziomu przeglądarki internetowej. Windows 8 dostarcza dwa rodzaje websocketów - MessageWebSocket i StreamWebSocket. Pierwszy z nich służy do transferu nieekstremalnych ilości danych, obsluguje UTF-8 i dane binarne, czasami jest porównywany do socketu UDP. StreamWebSocket służy do przesyłania dużych danych multimedialnych, obsługuje tylko dane binarne, czasami jest porównywany do socketu TCP. Websockety w Windows 8 podobnie jak inne sockety mają odpowiadające im kontrolki i klasy *Information. StreamWebSocketControl pozwala ustawić takie parametry jak używanie algorytmu Nagle, rozmiar bufora, nagłówki z danymi uwierzytelniającymi do serwera i jego proxy, można odczytać wspierane protokoły. W przypadku MessageWebSocketControl ustawić możemy maksymalny rozmiar wiadomości, jej rodzaj (czy UTF-8 czy binarna), rozmiar bufora, nagłówki z danymi uwierzytelniającymi do serwera i jego proxy, odczytać możemy wspierane protokoły. Websockety także wspierają bezpieczne połączenia.

Obecnie można zauważyć jeszcze większe podkreślenie protokołu Wifi-Direct przy omawianiu komunikacji przez zbliżenie lub dotyk. Wifi-Direct to stosunkowo nowy wynalazek pozwalający na bezpośrednią komunikację dwóch urządzeń bez potrzeby hot-spotu Wi-fi! Kolejne dobre zagadnienie do wykładania na uczelni –:) Windows 8 może użyć tego protokołu do wyszukania innych urządzeń z Windows 8 znajdujących się w pobliżu.

Jeśli chodzi o obsługę informacji o połączeniach, to obecnie moją uwagę zwrócił fakt, że można dostać także informacje dla podanego okresu czasu (ilość danych wysłanych i otrzymanych) oraz szczegółowe dane związane ze sprzętem (id karty LAN, numer portu, itd.).

W przypadku usługi transferu danych w tle uwzględniane są koszty połączenia i plan abonamentowy.

Klient Atom/Pub może obsługiwać również komunikację wymagającą danych uwierzytelniających.

JS

Advanced developers may also choose to optimize app behavior when retrying network operations. For example, you may want to replace an existing connection with a new connection over a higher speed network. In this scenario, a developer can use the Sockets APIs, like StreamSocketInformation.BandwidthStatistics, to determine if switching to another connection is appropriate.

Connecting to a web service

Downloading different types of content

You can download different types of content by when you use the responseType option of WinJS.xhr. This feature is new in XMLHttpRequest for Internet Explorer 10 and Metro style apps using JavaScript.

The following types are supported:

  • arraybuffer: The response is an ArrayBuffer. This is used to represent binary content as an array of type Int8, Int64, or other integer or float type.  responseText and responseXML are undefined.

WinJS.xhr({ url: "http://www.microsoft.com", responseType: "arraybuffer" })
    .done(function complete(result) {
        var arrayResponse = result.response;
        var dataview = new DataView(arrayResponse);
        var ints = new Uint32Array(arrayResponse.byteLength / 4);

        xhrDiv.style.backgroundColor = "#00FF00";
        xhrDiv.innerText = "Array is " + ints.length + "uints long";
    });

  • blob: The response is a Blob. This is used to represent binary content as a single binary entity. responseText and responseXML are undefined.


WinJS.xhr({ url: "http://www.microsoft.com/windows/Framework/images/win_logo.png", responseType: "blob" })
    .done(
        function (request) {
             var imageBlob = URL.createObjectURL(request.response);
            var imageTag = xhrDiv.appendChild(document.createElement("image"));
      imageTag.src = imageBlob;
     });

  • document: The type of response is an XML Document Object Model (DOM) object.. This is used to represent XML content, that is, content that has a MIME type of "text/xml".

  • json: The type of response is String. This is used to represent JSON strings. responseText is also of type String, and responseXML is undefined.

  • ms-stream: The type of response is msstream, and responseText and responseXML are undefined. This response type is not defined in the W3C spec, but it is supported to make it easier to handle streaming data.

  • text (the default): The type of response and responseText is String.


WinJS.xhr({ url: "http://www.msdn.microsoft.com/library", responseType: "text"
    .done(
        function (request) {
            var text = request.responseText;
            var subText = text.substring(text.indexOf("Welcome"), text.indexOf("services.") + 9);
            xhrDiv.innerHTML = subText;
    });

How to get uncached resources with WinJS.xhr

When you request Web resources by using WinJS.xhr, the response may be cached, which means that later requests will return the version of the resource that already exists on the client rather than resending the request. However, you can add a HTTP header that makes sure the request is sent again, even if it has already been cached.

Add the If-Modified-Since header to the headers property of the options parameter to the WinJS.xhr function.

WinJS.xhr({
    url: "http://www.microsoft.com",
    headers: {
        "If-Modified-Since": "Mon, 27 Mar 1972 00:00:00 GMT"
    } })
    .done(function complete(result) {
        // Report download.
        xhrDiv.innerText = "Downloaded the page";
        xhrDiv.style.backgroundColor = "#00FF00";
});

Setting timeout values with WinJS.xhr

When you use XMLHttpRequest you can set timeout values directly, but you cannot do so when you use WinJS.xhr. However, there is a way to set timeouts on WinJS.Promise objects. By calling WinJS.Promise.timeout, you ensure that the request is canceled if it has not completed within the specified time. Note that WinJS.Promise.timeout does not override the timeout value set on the XMLHttpRequest object, so there is no way to increase the default timeout set on XMLHttpRequest, only decrease it.

var xhrDiv = document.getElementById("xhrReport");

WinJS.Promise.timeout(1500, WinJS.xhr({ url: "http://www.microsoft.com" })
    .then(function complete(result) {
        xhrDiv.innerText = "Downloaded the page";
        xhrDiv.style.backgroundColor = "#00FF00";
        },
        function error(error){
            // The error thrown when xhr is cancelled has a message property, not a statusText property.
            if (error.statusText)
                xhrDiv.innerHTML = "Got error: " + error.statusText;
            else
                xhrDiv.innerHTML = "Got error: " + error.message;
            xhrDiv.style.color = "#000000";
            xhrDiv.style.backgroundColor = "#FF0000";
        },
        function progress(result) {
            xhrDiv.innerText = "Ready state is " + result.readyState;
            xhrDiv.style.color = "#000000";
            xhrDiv.style.backgroundColor = "#0000FF";
    }));

Connecting to network services

The primary classes for use with sockets include the following:

  • DatagramSocket - Used to support network communication using a UDP datagram socket.
  • StreamSocket - Used to support network communication using a TCP stream socket.
  • StreamSocketListener - Used to support listening for an incoming network connection using a TCP stream socket.

Windows 8 Consumer Preview also introduces a new type of socket, a WebSocket (the MessageWebSocket and StreamWebSocket classes).

How to use advanced socket controls

The DatagramSocket, StreamSocket, and StreamSocketListener classes all follow the same model for using advanced controls. Corresponding with each of the above primary classes are related classes to access advanced controls:

Datagram socket controls

The advanced options on the DatagramSocket are limited to a single option:

The quality of service can be set to one of the two possible values for the SocketQualityOfService enumeration. The normal setting is the default when a DatagramSocket is created. The lowLatency setting increases thread priority for receiving packets. This option would normally only be used for audio or similar apps that are very timing sensitive.

var clientSocket = new Windows.Networking.Sockets.DatagramSocket();

// Get the control object associated with this socket
var datagramControl = clientSocket.Control;

// Get the current setting for quality of service
// This isn't needed, but it shows how to get the current setting
var currentSetting = datagramControl.QualityOfService;

// Set quality of service to low latency
datagramControl.QualityOfService = lowLatency;
  
// Now use the DatagramSocket to call:
// BindEndpointAsync, BindServiceNameAsync,
// ConnectAsync, GetOutputstreamAsync, or
// JoinMulticastGroup

StreamSocket socket controls

There are several advanced options on the StreamSocket:

The default setting when a StreamSocket is created is that this option is set to true to disable the Nagle's algorithm. This setting reduces the potential delays when sending small messages. However, if the StreamSocket will be used for an app that sends many small packets and latency is not an issue, then Nagle's algorithm could be enabled to improve efficiency.

var clientSocket = new Windows.Networking.Sockets.StreamSocket();

// Get the control object associated with this socket
var streamControl = clientSocket.Control;

// Get the current setting for this option
// This isn't needed, but it shows how to get the current setting
var currentSetting = streamControl.NoDelay;

// Don't disable the nagle algorithm
streamControl.NoDelay = false;
  
// Now you can use the StreamSocket to call one of the
// ConnectAsync methods

StreamSocketListener socket controls

The advanced options on the StreamSocketListener are limited to a single option:

Remarks

In addition to control data, there are a similar set of related classes that provide access to additional socket information on these primary classes:

There is one significant differences between the socket information and socket control classes. The properties on a StreamSocketControl instance are readable or writable (get or set). In contrast, the properties on a StreamSocketInformation instance are read-only (get). An app may retrieve the value of a property on a StreamSocketControl or StreamSocketInformation instance at any time after the StreamSocket was created. However, an app must always set a property on a StreamSocketControl instance before issuing a connect operation or an operation that will bind the socket.

How to secure socket connections with TLS/SSL

The StreamSocket object can be configured to use SSL/TLS for communications between the client and the server. This support for SSL/TLS is limited to using the StreamSocket object as the client in the SSL/TLS negotiation. SSL/TLS cannot currently be used by the StreamSocketListener with the StreamSocket created when a connection is received to enable SSL/TLS on the StreamSocket created, since the SSL/TLS negotiation as a server is not implemented for a StreamSocket.

There are two ways to secure a StreamSocket connection with SSL/TLS:

  • ConnectAsync - Make the initial connection to a network service and negotiate immediately to use SSL/TLS for all communications.
  • UpgradeToSslAsync - Connect initially to a network service without encryption. The app may send or receive data. Then, upgrade the connection to use SSL/TLS for all further communications.
Use ConnectAsync

There are two ConnectAsync methods that support passing a protectionLevel parameter:

If the protectionLevel parameter is set to Windows.Networking.Sockets.SocketProtectionLevel.Ssl when calling either of the above ConnectAsync methods, the StreamSocket that must use SSL/TLS for encryption. This value requires encryption and never allows a NULL cipher to be used.

Use UpgradeToSslAsync

This uses the following method:

The UpgradeToSslAsync method takes two parameters. The protectionLevel parameter indicates the protection level desired. The validationHostName parameter is the hostname of the remote network destination that is used for validation when upgrading to SSL. Normally the validationHostName would be the same hostname that the app used to initially establish the connection. If the protectionLevel parameter is set to Windows.System.Socket.SocketProtectionLevel.Ssl when calling the above UpgradeToSslAsync method, the StreamSocket must use the SSL/TLS for encryption. This value requires encryption and never allows a NULL cipher to be used.

Remarks

The SocketProtectionLevel enumeration has three possible values:

  • PlainSocket - A plain socket with no encryption.
  • Ssl - A socket that must use the SSL/TLS for encryption. This value requires encryption and never allows a NULL cipher.
  • SslAllowNullEncryption - A socket that prefers to use the SSL/TLS for encryption. This value prefers that full encryption be used, but allows a NULL cipher (no encryption) based on the server configuration.

The SslAllowNullEncryption value is not normally used since it would allow a NULL cipher to be used, which represents no encryption, so network communication might not be encrypted. The SslAllowNullEncryption value does allow the SSL/TLS negotiation to validate the server based on the server digital certificate and the certificate authority.

If the SslAllowNullEncryption value is used, then the SSL strength actually negotiated using ConnectAsync or UpgradeToSslAsync can be determined by getting the StreamSocketinformation.SslStrength property.

Connecting to a WebSocket service

The WebSocket Protocol defines a mechanism for two-way communication between a client and a server.

To establish a WebSocket connection, a specific, HTTP-based handshake is exchanged between the client and the server. If successful, the application-layer protocol is "upgraded" from HTTP to WebSockets, using the previously established TCP connection. Once this occurs, HTTP is completely out of the picture; data can be sent or received using the WebSocket protocol by either endpoint at any time, until the WebSocket connection is closed.

Important A client cannot use WebSockets to transfer data unless the server also uses the WebSocket protocol.

Windows 8 Consumer Preview provides support for both client and server use of WebSockets. The Windows.Networking.Sockets namespace defines two types of WebSocket objects for use by clients in Metro style apps: MessageWebSocket and StreamWebSocket.

MessageWebSocket

  • Suitable for typical scenarios where messages are not extremely large.
  • Enables notification that an entire WebSocket message has been received.
  • Supports both UTF-8 and binary messages.
  • Somewhat comparable to a UDP socket (DatagramSocket).

StreamWebSocket

  • Suitable for scenarios in which large files (such as photos or movies) are being transferred.
  • Allows sections of a message to be read with each read operation.
  • Supports only binary messages.
  • Somewhat comparable to a TCP socket (StreamSocket).

Connecting using a MessageWebSocket

function startSend() {
    if (!messageWebSocket) {
        // Set up the socket data format and callbacks
        var webSocket = new Windows.Networking.Sockets.MessageWebSocket();
        // Both utf8 and binary message types are supported. If utf8 is specified then the developer
        // promises to only send utf8 encoded data.
        webSocket.control.messageType = Windows.Networking.Sockets.SocketMessageType.utf8;
        webSocket.onmessagereceived = onMessageReceived;
        webSocket.onclosed = onClosed;

        var serverAddress = new Windows.Foundation.Uri(document.getElementById("serverAddress").value);
        // Log the URI that your app will attempt to connect to.

        webSocket.connectAsync(serverAddress).then(function () {
            // Log that the connection was made.
            messageWebSocket = webSocket;
            // The default DataWriter encoding is utf8.
            messageWriter = new Windows.Storage.Streams.DataWriter(webSocket.outputStream);
            // Log that the message has been sent.
            messageWriter.writeString(document.getElementById("inputField").value);
            messageWriter.storeAsync().then("", sendError);
        }, function (e) {
            // The connection failed - log the error or take a specific action.
        });
    }
    else {
        // Log that the connection has already been made.
        sendMessage();
    }
}

function onMessageReceived(args) {
    // The incoming message is already buffered.
    var dataReader = args.getDataReader();
    // Log data about the received message here.
}

Connecting using a StreamWebSocket

function start() {
        if (streamWebSocket) {
            // The web socket is already running. Go ahead and immediately return, or log a debug message that indicates that it is running .
            return;
        }
  
        var webSocket = new Windows.Networking.Sockets.StreamWebSocket();
        webSocket.onclosed = onClosed;

        var uri = new Windows.Foundation.Uri(document.getElementById("serverAddress").value);

        // Log the URI that your app will attempt to connect to, here.

        webSocket.connectAsync(uri).then(function () {
           
            streamWebSocket = webSocket;
            dataWriter = new Windows.Storage.Streams.DataWriter(webSocket.outputStream);
            dataReader = new Windows.Storage.Streams.DataReader(webSocket.inputStream);

            // When buffering, return as soon as any data is available.
            dataReader.inputStreamOptions = Windows.Storage.Streams.InputStreamOptions.partial;
            countOfDataSent = 0;
            countOfDataReceived = 0;

            // Continuously send data to the server
            writeOutgoing();

            // Continuously listen for a response
            readIncoming();

        }, function (e) {
            // The connection failed. Log the error or take a specific action here.
        });
    }

function writeOutgoing() {
    try {
        var size = dataWriter.measureString(data);
        countOfDataSent += size;
        document.getElementById("dataSent").value = countOfDataSent;
        dataWriter.writeString(data);
        dataWriter.storeAsync().then(function () {
            // Add a 1 second delay so the user can see what's going on.
            setTimeout(writeOutgoing, 1000);
        }, writeError);
    }
    catch (e) {
        writeError(e);
        });
}

function readIncoming(args) {
    // Buffer as much data as you require for your protocol.
    dataReader.loadAsync(100).then(function () {
        countOfDataReceived += dataReader.unconsumedBufferLength;
        document.getElementById("dataReceived").value = countOfDataReceived;

        var data;
        dataReader.readBytes(data);

        // Do something with the data.
        // Alternatively you can use DataReader to read out individual booleans,
        // ints, strings, etc.  Be sure to check that UnconsumedBufferLength has
        // enough data for you first.

        readIncoming(); // Start another read
    }, readError);
}

How to use advanced WebSocket controls

The MessageWebSocket and StreamWebSocket classes follow the same model for using advanced controls. Corresponding with each of the above primary classes are related classes to access advanced controls:

Advanced StreamWebSocket controls

There are several advanced options on the StreamWebSocket.

Advanced MessageWebSocket controls

Many of the advanced options on the MessageWebSocket are the same as those on the StreamWebSocket, but there are some differences.

Remarks

In addition to control data, there are a similar set of related classes that provide access to additional information on these primary classes:

How to secure WebSocket connections with TLS/SSL

In most cases you'll want to use a secure WebSocket connection. This will increase the chances that your connection will succeed, as many proxies will reject unencrypted WebSocket connections.

The WebSocket Protocol defines two URI schemes. ws: is used for unencrypted connections, while wss: is used for secure connections that should be encrypted.

To encrypt your connection, use the wss: URI scheme.

Supporting proximity and tapping

Proximity enables you to connect computers with a simple tap gesture. If two computers come within 3-4 centimeters of each other, or are tapped together, the operating system of each computer becomes aware of the other computer. You can also connect two computers running your application that are within wireless range, by using peer browsing with Wi-Fi Direct.

A computer must have a proximity device, such as a Near Field Communication (NFC) radio device, to enable tap connections. A computer must have a Wi-Fi device that supports Wi-Fi Direct to enable peer browsing.

You can use proximity to enable a quick exchange of data during a tap gesture. Or, you can use the tap to set up a long-term communication channel using TCP/IP, Wifi-Direct, or Bluetooth. You do not need to be connected to a network.

// This function responds to all application activations.
app.onactivated = function (eventObject) {
    if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

         displayNameTextBox.value = Windows.Networking.Proximity.PeerFinder.displayName;
        Windows.Networking.Proximity.PeerFinder.addEventListener("connectionrequested", connectionRequested);

        WinJS.UI.processAll();
    }
};

function id(elementId) {
    return document.getElementById(elementId);
}

The code in the event handler for the advertiseForPeersButton button sets the peer name for the local computer and starts the PeerFinder. If triggered connections are supported (tapping), the code identifies the event handler for the triggeredConnectionStateChanged event. In the triggeredConnectionStateChanged event handler, the code opens a stream socket to send text messages between the peer apps.

// Click event for "Advertise" button.
function advertiseForPeers() {
    Windows.Networking.Proximity.PeerFinder.displayName = displayNameTextBox.Text;
    Windows.Networking.Proximity.PeerFinder.start();

    if (Windows.Networking.Proximity.PeerFinder.supportedDiscoveryTypes &
        Windows.Networking.Proximity.PeerDiscoveryTypes.triggered) {

            Windows.Networking.Proximity.PeerFinder.addEventListener(
                "triggeredconnectionstatechanged", triggeredConnectionStateChanged);

            id("messageDiv").innerHTML +=
                "You can tap to connect a peer device that is " +
                "also advertising for a connection.<br />";
   } else {
        id("messageDiv").innerHTML +=
            "Tap to connect is not supported.<br />";
    }

    if (!(Windows.Networking.Proximity.PeerFinder.SupportedDiscoveryTypes &
          Windows.Networking.Proximity.PeerDiscoveryTypes.Browse)) {
        id("messageDiv").innerHTML +=
    "Peer discovery using Wifi-Direct is not supported.<br />";
    }
}

function triggeredConnectionStateChanged(e) {
    if (e.state === Windows.Networking.Proximity.TriggeredConnectState.peerFound) {
        id("messageDiv").innerHTML +=
            "Peer found. You may now pull your devices out of proximity.<br />";
    }
    if (e.state === Windows.Networking.Proximity.TriggeredConnectState.completed) {
        id("messageDiv").innerHTML += "Connected. You may now send a message.<br />";
        sendMessage(e.socket);
    }
}

The code in the event handler for the findPeersButton button calls the findAllPeersAsync method to browse for devices within wireless range. When one or more peers is found, the sample calls the connectAsync method to connect to the first peer that is found. This is for sample purposes only. You should present the user with a list of possible peers to choose from, and then connect to the peer that the user chooses.

// Click event for "Browse" button.
function findPeers() {
    if (Windows.Networking.Proximity.PeerFinder.supportedDiscoveryTypes &
        Windows.Networking.Proximity.PeerDiscoveryTypes.browse) {

            Windows.Networking.Proximity.PeerFinder.findAllPeersAsync().done(
        function (peerInfoCollection) {
            if (peerInfoCollection.length > 0) {
                // Connect to first peer found - example only.
                // In your app, provide the user with a list of available peers.
                connectToPeer(peerInfoCollection[0]);
            }
        },
        function (err) {
            id("messageDiv").innerHTML += "Error finding peers: " + err + "<br />";
        });
    } else {
        id("messageDiv").innerHTML +=
        "Peer discovery using Wi-Fi Direct is not supported.<br />";
    }
}

function connectToPeer(peerInfo) {
    id("messageDiv").innerHTML += ("Peer found. Connecting to " + peerInfo.displayName + "<br />");
    Windows.Networking.Proximity.PeerFinder.connectAsync(peerInfo).done(
        function (socket) {
            id("messageDiv").innerHTML += "Connection successful. You may now send messages.<br />";
            sendMessage(socket);
        },
        function (err) {
            id("messageDiv").innerHTML += "Connection failed: " + err + "<br />";
        });

    requestingPeer = null;
}

The code in the event handler for the stopFindingPeersButton button calls the stop method. This method stops the event handler from advertising and browsing for peers, whether by a wireless connection or from a tap gesture.

function stopFindingPeers() {
    Windows.Networking.Proximity.PeerFinder.stop();
    if (proximitySocket) { proximitySocket.close(); }
}

The code includes an event handler for the connectionRequested event that occurs when a peer opens a connection with you by calling the connectAsync method. You can click the Accept Connection button to accept the connection.

// Handle external connection requests.
var requestingPeer;

function connectionRequested(e) {
    id("messageDiv").innerHTML +=
        "Connection requested by " + e.peerInformation.DisplayName + ". " +
        "Click 'Accept Connection' to connect.";
    requestingPeer = e.PeerInformation;
}

function acceptConnection() {
    if (requestingPeer == null) {
        id("messageDiv").innerHTML +="No peer connection has been requested.";
        return;
    }

    connectToPeer(requestingPeer);
}

When a successful connection is made, the code passes the ProximityStreamSocket object created by the connection to the sendMessage function. The sendMessage function opens a network connection with the proximate computer. This enables you to send messages back and forth. Be sure to always call the close method of the ProximityStreamSocket object when you are finished with it.

var proximitySocket;
var dataWriter;

// Reference socket streams for writing and reading messages.
function sendMessage(socket) {
    id("sendMessageButton").addEventListener("click", sendMessageText);

    // Get the network socket from the proximity connection.
    proximitySocket = socket;

    // Create DataWriter for writing messages to peers.
    dataWriter = new Windows.Storage.Streams.DataWriter(proximitySocket.outputStream);

    // Listen for messages from peers.
    var dataReader = new Windows.Storage.Streams.DataReader(proximitySocket.inputStream);
    startReader(proximitySocket, dataReader);
}

// Send a message to the socket.
function sendMessageText() {
    var msg = id("sendMessageText").value;

    if (msg.length > 0) {
        var msgLength = dataWriter.measureString(msg);
        dataWriter.writeInt32(msgLength);
        dataWriter.writeString(msg);
        dataWriter.storeAsync().done(
            function (byteCount) {
                id("messageDiv").innerHTML += "Message sent: " + msg + "<br />";
            },
            function (err) {
                id("messageDiv").innerHTML += "Send error: " + err.message + "<br />";
            });
    }
}

// Read out and print the message received from the socket.
function startReader(socket, reader) {
    var initialLength = 4;
    reader.loadAsync(initialLength).done(
    function () {
        var msgLength = reader.readInt32();
        reader.loadAsync(msgLength).done(
            function () {
                var message = reader.readString(msgLength);
                id("messageDiv").innerHTML += "Received message: " + message + "<br />";

                // After receiving a message, listen for the next message.
                startReader(socket, reader);
            },
            function (err) {
                id("messageDiv").innerHTML += "Error: " + err.message + "<br />";
                socket.close();
            });
    });
}

Accessing connection and data plan information

Retrieving network connection information

The NetworkInformation class defines two methods for ConnectionProfile retrieval. The GetInternetConnectionProfile method will return only the profile associated with the Internet connection.

Calling the GetConnectionProfiles method retrieves profiles for all connections currently established on the device, including the Internet connection.

How to retrieve connection usage data for a specific period of time

To retrieve the data we need, the system DateTime (currTime), and a startTime value are passed to the getLocalUsage method and a DataUsage object is returned containing the sent and received values, in bytes, for the requested time period.

For mobile app scenarios, you can add a RoamingStates value to the getLocalUsage call to scope the requested traffic data to a particular roaming state.

var networkInfo = Windows.Networking.Connectivity.NetworkInformation;

function DisplayLocalDataUsage() {
    var currTime = new Date();

    //Set start Time to 1 hour (3600000ms) before current time
    var startTime = new Date(currTime - 3600000);

    //Get the ConnectionProfile that is currently used to connect to the Internet
    var connectionProfile = networkInfo.getInternetConnectionProfile();
    var LocalUsage = connectionProfile.getLocalUsage(startTime, currTime);
    var lclString = "Local Data Usage: \n\r";
    lclString += "Bytes Sent: " + LocalUsage.bytesSent + "\n\r";
    lclString += "Bytes Received: " + LocalUsage.bytesReceived + "\n\r";
}

How to retrieve network adapter and locality information

This topic demonstrates how to retrieve LanIdentifier objects associated with adapters on a network, and access information that can be used to determine the location within the network infrastructure.

A LanIdentifier object defines methods an app uses to retrieve infrastructure/port ID values for determining location, and the ID value associated with the network adapter itself, which is accessed via a ConnectionProfile to show association with a network connection.

Retrieve all LanIdentifier objects

var networkInfo = Windows.Networking.Connectivity.NetworkInformation;

function DisplayLanIdentifiers() {
    var lanIdentifier = "";
    try {
        var lanIdentifiers = networkInfo.getLanIdentifiers();
        if (lanIdentifiers.length !== 0) {
            lanIdentifier += "Number of LAN Identifiers retrieved: " + lanIdentifiers.length + "\n\r";
            lanIdentifier += "=============================================\n\r";
            for (var i = 0; i < lanIdentifiers.length; i++) {
                lanIdentifier += getLanIdentifierData(lanIdentifiers[i]);
                lanIdentifier += "----------------------------------------------------------------\n\r";
            }
            mySample.displayStatus(lanIdentifier);
        }
        else {
            mySample.displayStatus("No LAN identifier data found");
        }
    }

    catch (e) {
        mySample.displayError("Exception Caught: " + e + "\n\r");
    }
}

Display the properties of a LanIdentifier object

var networkInfo = Windows.Networking.Connectivity.NetworkInformation;
 
function getLanIdentifierData(lanIdentifier) {
    var lanIdentifierData = "";
    var i = 0;
    try {
        if (lanIdentifier === null) {
            return "";
        }
        if (lanIdentifier.infrastructureId !== null) {
            lanIdentifierData += "Infrastructure Type: " + lanIdentifier.infrastructureId.type + "\n\r";
            lanIdentifierData += "Infrastructure Value: [";
            for (i = 0; i < lanIdentifier.infrastructureId.value.length; i++) {
                lanIdentifierData += lanIdentifier.infrastructureId.value[i].toString(16) + " ";
            }
            lanIdentifierData += "]\n\r";
        }
        if (lanIdentifier.portId !== null) {
            lanIdentifierData += "Port Type : " + lanIdentifier.portId.type + "\n\r";
            lanIdentifierData += "Port Value: [";
            for (i = 0; i < lanIdentifier.portId.value.length; i++) {
                lanIdentifierData += lanIdentifier.portId.value[i].toString(16) + " ";
            }
            lanIdentifierData += "]\n\r";
        }
        if (lanIdentifier.networkAdapterId !== null) {
            lanIdentifierData += "Network Adapter Id : " + lanIdentifier.networkAdapterId + "\n\r";
        }
    }

    catch (e) {
        mySample.displayError("Exception Caught: " + e + "\n\r");
    }
    return lanIdentifierData;
}

Transferring data in the background

The Background Transfer feature maintains the integrity of each file transfer operation when network status changes occur, intelligently leveraging connectivity and carrier data-plan status information provided by the Connectivity feature for each connection established on the machine.

For example, download transfers are paused automatically when a user hits a carrier-defined data cap in order to prevent additional charges during the current billing cycle, with the paused transfer automatically resuming when a connection to a "unrestricted" network has been established.

Accessing and managing syndicated content

Note that, for publication of syndicated content, the Windows Runtime implementation of Atom Publication (Windows.Web.AtomPub) only supports feed content operations according to the Atom and Atom Publication standards.

Accessing a web feed

function getFeedWithSyndicationClient(feedUri) {
    var client = new Windows.Web.Syndication.SyndicationClient();
    var uri = Windows.Foundation.Uri(feedUri);
    var promise = client.retrieveFeedAsync(uri);
    var completed = function (SyndicationFeed) {
        var items = SyndicationFeed.items;
        var feedIndex = 0;

        while (feedIndex &lt; items.size) {
            var item = items.getAt(feedIndex);
            displayItemInOutputArea(item);

            feedIndex++;
        }
    }

    promise.then(completed);
}

function displayItemInOutputArea(item) {
    var outputArea = document.getElementById("outputArea");
    var title = item.title ? item.title.text : "";
    var summary = item.summary ? item.summary.text : "";
    var link = (item.links.size &gt; 0 &amp;&amp; item.links.getAt(0) !== null &amp;&amp; item.links.getAt(0).uri !== null) ? item.links.getAt(0).uri.absoluteUri : "";
    var author = (item.authors.size &gt; 0 &amp;&amp; item.authors.getAt(0) !== null) ? item.authors.getAt(0).name : "";
    var lastUpdated = item.lastUpdatedTime ? item.lastUpdatedTime : new Date();
}

Managing feed entries

Initializing the client with authentication credentials

Additionally, most web publication services will require some form of authentication, a functionality provided by the Windows.Security namespace.

var credential = new Windows.Security.Credentials.PasswordCredential(feedUri, user_, password_);

var client = new Windows.Web.AtomPub.AtomPubClient(credential);

Retrieving a service document

<?xml version="1.0" encoding='utf-8'?>
<service xmlns="http://www.w3.org/2007/app"
         xmlns:atom="http://www.w3.org/2005/Atom">
    <workspace>
        <atom:title>Main Site</atom:title>
        <collection
            href="http://example.org/blog/main" >
            <atom:title>My Blog Entries</atom:title>
            <categories
               href="http://example.com/cats/forMain.cats" />
        </collection>
        <collection
            href="http://example.org/blog/pic" >
            <atom:title>Pictures</atom:title>
            <accept>image/png</accept>
            <accept>image/jpeg</accept>
            <accept>image/gif</accept>
        </collection>
    </workspace>
</service>

function getServiceDocument (serviceUrl, processServiceDocumentCallback) {
    var uri = new Windows.Foundation.Uri(serviceUrl);
    client.retrieveServiceDocumentAsync(uri, processServiceDocumentCallback).then(

    processServiceDocumentCallback(result)
    );
}

To retrieve, edit, or delete specific feed entries an app will need to parse a retrieved ServiceDocument for the absolute URIs associated with individual entries.

function getEntryLinkFromSD(ServiceDocument, setEntryLinkCallback) {
    var findEntryLink = function (ServiceDocument) {

        var workspace = ServiceDocument.workspaces.getAt(0);

        var j = 0;
        for (j = 0; j < workspace.collections.size; j++) {
            if (workspace.collections[j].accepts[0] === "application/atom+xml;type=entry") {
                setEntryLinkCallback(workspace.collections[j].uri.absoluteUri);
            }
        }
    };
}

Creating a new post within a collection

function createPost (postUrl, postTitle, postContent, postSummary, postAuthor) {

    var uri = new Windows.Foundation.Uri(postUrl);
    var syndicationItem;
    syndicationItem = new Windows.Web.Syndication.SyndicationItem(postTitle,
        new Windows.Web.Syndication.SyndicationContent(postContent, Windows.Web.Syndication.SyndicationTextType.Text),
        null);
    syndicationItem.summary = new Windows.Web.Syndication.SyndicationText(postSummary);
    syndicationItem.authors[0] = new Windows.Web.Syndication.SyndicationPerson(postAuthor);

    client.createResourceAsync(uri, syndicationItem, outputCallback).then(
           
    outputCallback(uri)
    );
}

Editing a post within a collection

function editPost (editUrl, postTitle, postContent, postSummary, postAuthor, refreshCallback) {

    var uri = new Windows.Foundation.Uri(editUrl);
    var syndicationItem;

    syndicationItem = new Windows.Web.Syndication.SyndicationItem();
    syndicationItem.title = new Windows.Web.Syndication.SyndicationText(postTitle);
    syndicationItem.summary = new Windows.Web.Syndication.SyndicationText(postSummary);
    syndicationItem.content = new Windows.Web.Syndication.SyndicationContent(postContent, Windows.Web.Syndication.SyndicationTextType.Text);
    syndicationItem.authors[0] = new Windows.Web.Syndication.SyndicationPerson(postAuthor);
    // Note: Also other item fields can be set such as 'syndicationItem.Categories[0]'

    client.updateResourceAsync(uri, syndicationItem, refreshCallback).then(
             
    refreshCallback(uri)
    );
}

Deleting a post from a collection

function deletePost(editUrl, refreshCallback) {
    var uri = new Windows.Foundation.Uri(editUrl);

    client.deleteResourceAsync(uri, refreshCallback).then(

        refreshCallback(uri)
    );
}

W przypadku C# trzeba pamiętać, że HttpClient obsługuje domyślny maksymalny rozmiar odpowiedzi 64kB i czasami trzeba ten parametr ustawić na większą wartość (sam natrafiłem na ten problem, gdy w budowanej aplikacji pobierałem pliki video). Jeśli webserwis, z którym się łączymy wymaga nagłówka user-agent możemy go zdefiniować ustawiając jawnie wartość property DefaultRequestHeaders. Pewną nowością wydaje się być klasa HtppClientHandler, za pomocą której możemy wymusić, aby nie były uwzględniane przekierowania w requeście. Obsługiwany jest też klasa generycznego handlera przeznaczona do dziedziczenia oraz klasa delegująca wykonanie do innego handlera. HttpClient oczywiście obsługuje też bezpieczną komunikację po HTTPS.

Na koniec – niejako przy okazji – zwróciło mają uwagę dostanie się do dispatchera “ogólnego” poprzez wywołanie Window.Current.CoreWindow.Dispatcher.

C# / C++

Connecting to a web service

Connecting using XML HTTP Request (IXHR2)

Use the IXMLHTTPRequest2 interface to send HTTP GET and POST requests to a web service.

IXMLHTTPRequest2 allows applications to run in a Multi-Threaded Apartment (MTA), a requirement for running under the Windows Runtime in Windows 8 Consumer Preview. Running in an MTA means that a thread that has access to an IXMLHTTPRequest2 object does not block on request or response processing for other IXMLHTTPRequest2 objects running in the same apartment.

This interface also implements a callback model for event handling. Because IXMLHTTPRequest2 methods allow only asynchronous method calls, to receive completion callbacks an application must pass a pointer to an IXMLHTTPRequest2Callback object when it first calls IXMLHTTPRequest2::Open to create an HTTP request.

Więcej informacji na Quickstart: Connecting using XML HTTP Request (IXHR2)

Connecting using HttpClient

The default size of the HttpClient.MaxResponseContentBufferSize property is 64K. This will result in an error if the response from the web service exceeds this value. To avoid some of these errors, we set this property to a larger value.

By default, no user-agent header is sent with the HTTP request to the web service by the HttpClient object. Some HTTP servers, including some Microsoft web servers, require that a user-agent header be included with the HTTP request sent from the client and return an error if no header is present. A user-agent header is added using the HttpClient.DefaultRequestHeaders property to avoid these errors.

httpClient.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");


private async void Start_Click(object sender, RoutedEventArgs e)
{
    try
    {
        string responseBodyAsText;
        OutputView.Text = "";
        StatusText.Text = "Waiting for response ...";

        HttpResponseMessage response = await httpClient.GetAsync(InputAddress.Text);
        response.EnsureSuccessStatusCode();

        StatusText.Text = response.StatusCode + " " + response.ReasonPhrase + Environment.NewLine;
        responseBodyAsText = await response.Content.ReadAsStringAsync();
        responseBodyAsText = responseBodyAsText.Replace("<br>", Environment.NewLine); // Insert new lines
        OutputView.Text = responseBodyAsText;
    }
    catch (HttpRequestException hre)
    {
        StatusText.Text = hre.ToString();
    }
    catch (Exception ex)
    {
        // For debugging
        StatusText.Text = ex.ToString();
    }
}

How to use HttpClient handlers

The HttpClientHandler allowes the app to set options on the HTTP request.

The AllowAutoRedirect property indicates if requests made by the HttpClientHandler object should follow redirection responses. The default value for this property is true. If this property is set to false, then requests from the web service to redirect the request will not be followed.

private HttpClient httpClient;
private HttpClientHandler handler;

public BlankPage()
{
    this.InitializeComponent();
    handler = new HttpClientHandler();
    handler.AllowAutoRedirect=false;

    httpClient = new HttpClient(handler);

    // Increase the max buffer size for the response to so we don't get an exception accessing so many web sites
    httpClient.MaxResponseContentBufferSize = 256000;
    // Add a user-agent header
    httpClient.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
}

There are several HTTP message handlers that can be used with the HttpClient class.

  • HttpClientHandler - The default message handler used by HttpClient discussed in this topic.
  • MessageProcessingHandler - A base type for HTTP message handlers. This is the easiest handler to derive from and should be the starting point for most custom handlers.
  • DelegatingHandler - A base type for HTTP handlers that delegate the processing of HTTP response messages to another handler. This handler is ideal for testing.

How to secure HttpClient connections

To encrypt your connection, use the https: URI scheme.

Proximity and tapping

Publishing and subscribing to messages using tapping

private Windows.UI.Core.CoreDispatcher messageDispatcher = Window.Current.CoreWindow.Dispatcher;

Brak komentarzy: