Monday Aug 18, 2014

Tyrus 1.8

Another version of Tyrus, the reference implementation of JSR 356 – Java API for WebSocket is out! Complete list of fixes and features is below, but let me describe some of the new features in more detail. All information presented here is also available in Tyrus documentation.

What’s new?

First to mention is that JSR 356 Maintenance review Ballot is over and the change proposed for 1.1 release was accepted. More details about changes in the API can be found in this article. Important part is that Tyrus 1.8 implements this API, meaning you can use Lambda expressions and some features of Nashorn without the need for any workarounds.

Almost all other features are related to client side support, which was significantly improved in this release. Firstly – I have to admit, that Tyrus client contained security issue – SSL Hostname verification was not performed when connecting to “wss” endpoints. This was fixed as part of TYRUS-339 and resulted in some changes in the client configuration API. Now you can control whether HostnameVerification should be performed (SslEngineConfigurator#setHostnameVerificationEnabled(boolean)) or even set your own HostnameVerifier (please use carefully): #setHostnameVerifier(…). Detailed description can be found in Host verification chapter.

Another related enhancement is support for Http Basic and Digest authentication schemes. Tyrus client now enables users to provide credentials and underlying implementation will take care of everything else. Our implementation is strictly non pre-emptive, so the login information is sent always as a response to 401 Http Status Code. If the Basic and Digest are not good enough and there is a need to use some custom scheme or something which is not yet supported in Tyrus, custom Authenticator can be registered and the authentication part of the handshake process will be handled by it. Please seeClient HTTP Authentication chapter in the user guide for more details.

There are other features, like fine-grain threadpool configuration for JDK client container, build-in Http redirect support and some reshuffling related to unifying the location of client configuration classes and properties definition – every property should be now part of ClientProperties class. All new features are described in the user guide – in chapterTyrus proprietary configuration.

Update – Tyrus 1.8.1

There was another slightly late reported issue related to running in environments with SecurityManager enabled, so this version fixes that. Another noteworthy fixes are TYRUS-355 and TYRUS-361; the first one is about incorrect thread factory used for shared container timeout, which resulted in JVM waiting for that thread and not exiting as it should. The other issue enables relative URIs in Location header when using redirect feature.

Links

Complete list of changes:

Bug

  • [TYRUS-333] – Multiple endpoints on one client
  • [TYRUS-334] – When connection is closed by a peer, periodic heartbeat pong is not stopped
  • [TYRUS-336] – ReaderBuffer.getNextChars() keeps blocking a server thread after client has closed the session
  • [TYRUS-338] – JDK client SSL filter needs better synchronization during handshake phase
  • [TYRUS-339] – SSL hostname verification is missing
  • [TYRUS-340] – Test PathParamTest are not stable with JDK client
  • [TYRUS-341] – A control frame inside a stream of continuation frames is treated as the part of the stream
  • [TYRUS-343] – ControlFrameInDataStreamTest does not pass on GF
  • [TYRUS-345] – NPE is thrown, when shared container timeout property in JDK client is not set
  • [TYRUS-346] – IllegalStateException is thrown, when using proxy in JDK client
  • [TYRUS-347] – Introduce better synchronization in JDK client thread pool
  • [TYRUS-348] – When a client and server close connection simultaneously, JDK client throws NPE
  • [TYRUS-356] – Tyrus cannot determine the connection port for a wss URL
  • [TYRUS-357] – Exception thrown in MessageHandler#OnMessage is not caught in @OnError method
  • [TYRUS-359] – Client based on Java 7 Asynchronous IO makes application unexitable

Improvement

  • [TYRUS-328] – JDK 1.7 AIO Client container – threads – (setting threadpool, limits, …)
  • [TYRUS-332] – Consolidate shared client properties into one file.
  • [TYRUS-337] – Create an SSL version of Basic Servlet test

New Feature

  • [TYRUS-228] – Add client support for HTTP Basic/Digest

Task

  • [TYRUS-330] – create/run tests/servlet/basic via wss
  • [TYRUS-335] – [clustering] – introduce RemoteSession and expose them via separate method (not include remote sessions in the getOpenSessions())
  • [TYRUS-344] – Introduce Client support for HTTP Redirect

Monday Jun 16, 2014

Tyrus 1.7

This release cycle was shorter comparing to previous ones and the main purpose was to integrate and align with upper stack projects. Despite that fact there are some new features and bugfixes:

What’s new?

Ondřej added nice feature which allows to limit opened WebSocket sessions. And it is configurable, currently supported scopes of limitations are “per endpoint”, “per client remote address” and “per application”. This should allow effective control of server-side resources. For now, the values are static and constant, but if there will be a demand for creating more advanced session limit support, like dynamic load-balancing among set of endpoints etc, we will provide that as well. Feel free to file an enhancement request if you are interested!

Petr improved monitoring support which was added in Tyrus 1.6 – now there is a possibility to get the information about errors reported to the endpoints. Errors are collected per Exception type thrown, so you might get number of decoding issues, IOExceptions and other custom Throwables used in the endpoint implementation.

Other than that, there were just bugfixes. Two of them are worth mentioning: Tyrus now supports close codes 1012 and 1013 (SERVICE RESTART and TRY AGAIN LATER). Both of these can be returned only from server endpoint. When client side will try to send those, they will be replaced with close code 1000.

And the other bugfix is about SSL/TLS support in Java 7 AIO based client container – the issue manifested when bigger messages were sent from the client to server. The handling was not correct in this case and it could happen that the message was not completely sent.

Complete list of changes

TYRUS-313 Limiting opened connections
TYRUS-319 Bug call EJB from WebSocket
TYRUS-320 When put under heavy load by multiple clients, server throws an exception.
TYRUS-321 tyrus-client incorrectly reports HTTP staus 500 for all non-101 responses
TYRUS-322 Some of session close-codes are not supported
TYRUS-324 Session.close() should close the connection without close reason.
TYRUS-325 Server do not close session properly if non-instantiable endpoint class is provided
TYRUS-326 Expose monitoring statistics about number of @OnError method invocations
TYRUS-327 JDK client container cannot handle “big” messages over SSL/TLS

Link

If you have any feedback, suggestions or improvement requests, please send us a note tousers@tyrus.java.net or create new issue at our JIRA instance.

Friday May 16, 2014

Tyrus 1.6

I’m pleased to announce that Tyrus 1.6 was released this week and there are some nice features and bugfixes in this release, so let me introduce most important ones.

What’s new?

First bigger addition is JDK Client Transport. We used exclusively Grizzly framework for handling transport layer on the client side and now we provide an option to switch to JDK 1.7 based transport. Main advantage of it is reduced size and number of jar files. The default transport is still based on Grizzly, so if you want to try JDK 1.7 AIO, you need to enable it explicitly. See JDK 7 client chapter in Tyrus documentation for more details. This feature was implemented by Petr Janouch, new Tyrus team member.

Another important feature is monitoring server side resource utilisation and exposing this info as statistics via JMX beans. Currently available statistics are mostly about number and type of received and send messages, also there is higher level part which provides list of deployed endpoints and number of concurrent connected clients. If you want to try this out, see JMX Monitoring chapter in Tyrus user guide for configuration details. This feature was also contributed by Petr.

Next in line is change related to an issue in JSR 356. Current version of Session interface does allow use of Lambda expressions when registering a message handler, but it does not work as expected. Problem is, that it just cannot work. The API forces implementation to get the generic type information during runtime, which is not always available (in Java). Most usecases do work fine, but one doesn’t – when the anonymous class implementation is replaced by Lambda expression, type information is lost. Tyrus now implements proposed solution, but to be able to access mentioned methods, you need to cast Session to TyrusSession. Then you need to provide type of the message handler (java.lang.Class) and the handler itself – since the type info is now separated, there is no need to get it from the message handler instance , thus it will work with any representation. We are working on porting this fix to the WebSocket API, stay tuned for more details.

Last and in this case maybe least feature is WSADL. Don’t worry if you don’t know what that is – nobody does :). WSADL is XML (for now) descriptor of deployed application, currently providing only set of deployed endpoints (mainly because other information, like registered message handlers, is not that easy to get before client connects to the endpoint and can vary per Session). WSADL stands for WebSocket Application Descriptor Language and it is supposed to be the same as WADL is for RESTful webservices and WSDL for SOAP webservices. In case you want to test this feature, enableWSADL_SUPPORT (or see the test) and GET /contextPath/application.wsadl on your deployed Tyrus application.

Complete list of changes

TYRUS-301 Custom String encoder is not used
TYRUS-305 Add support for multiple client container to TestContainer
TYRUS-311 Session timeout on client does not work when set in onOpen method
TYRUS-312 TyrusFuture.get(long,TimeUnit) does not honor Future.get(long,TimeUnit) contract
TYRUS-293 Automatic heartbeat PING
TYRUS-259 Should produce a warning during deployment when OnMessage#maxMessageSize is larger than the value of org.glassfish.tyrus.servlet.incoming-buffer-size
TYRUS-308 JDK Client transport – SSL support
TYRUS-309 JDK Client transport – Proxy support
TYRUS-318 Writer returned from BasicRemote.getSendWriter() throws NPE when flush is called more than once.
TYRUS-314 Create WADL-like descriptor per deployed app
TYRUS-317 Allow server configuration using WebSocketContainer or WebSocketAddOn
TYRUS-302 Java 8 Lambda
TYRUS-214 Expose monitoring API/statistics
TYRUS-299 Missing tyrus-container-grizzly-server in the release package WebSocket RI archive
TYRUS-233 Provide client transport based on plain JDK
TYRUS-310 When max idle timeout is reset to 0, every received message or sent ping or pong causes an empty task to be scheduled and executed

Links

If you have any questions or comments or if you want to discuss anything related to Tyrus, easiest way to contact us is via users@tyrus.java.net mailing list.

Wednesday Feb 12, 2014

Oracle Toplink team is hiring!

Oracle Toplink team is looking for Java Developers! If you want to became part of the team which is working on Toplink, EclipseLink, JAX-B, JAX-WS and related projects and products, this is the time to update your CV and share it with us. This job position is based in Prague, Czech Republic.

Formal job offer can be found on linked.in.

If you are interested and have any questions, feel free to contact me directly via this blog or via pavel.bucek at oracle.com.

Wednesday Jan 08, 2014

WebSocket Extensions in Tyrus

There is always room for another experimental feature :-) This one is maybe little less experimental than broadcast support, but please implement presented APIs with one important fact in your mind – it can change any time.

What is WebSocket Extension?

You can think of WebSocket Extension as a filter, which processes all incoming and outgoing frames. Frame is the smallest unit in WebSocket protocol which can be transferred on the wire – it contains some some metadata (frame type, opcode, payload length, etc.) and of course payload itself.

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

Figure taken from RFC 6455

If you are interested about some more details related to WebSocket protocol specification, please see linked RFC document (RFC 6455).

What can be achieved by WebSocket Extension?

Almost everything. You can change every single bit of incoming or outgoing frame, including control frames (close, ping and pong). There are some RFC drafts trying to standardise extensions like per message compression (used to be per frame compression) and multiplexing extension (now expired).

Tyrus already has support for per message compression extension and exposes interfaces which allow users to write custom extensions with completely different functionality.

When should I consider implementing WebSocket Extensions?

This is maybe the most important question. WebSocket Extensions can do almost everything, but you should not use them for use cases achievable by other means. Why? Majority of WebSocket use cases are about communication with browsers and javascript client cannot really influence which exception is going to be used. Browser must support your particular extension (by default or it can be enabled by some custom module).

You can easily use custom extension when using Tyrus java client, so if there is no browser interaction in your application, it should be easier to distribute your extensions to involved parties and you might lift the threshold when deciding whether something will be done by extension or by application logic.

Java API for WebSocket and Extensions

API currently contains following extension representation (javadoc removed):

1
2
3
4
5
6
7
8
9
10
public interface Extension {
String getName();
List getParameters();
interface Parameter {
String getName();
String getValue();
}
}

and the specification (JSR 356) limits extension definition only for handshake purposes. To sum that up, users can only declare Extension with static parameters (no chance to set parameters based on request extension parameters) and that’s it. These extensions don’t have any processing part, so the work must be done somewhere else. As you might already suspect, this is not ideal state. Usability of extensions specified like this is very limited, it is basically just a marker class which has some influence on handshake headers. You can get list of negotiated extensions in the runtime (Session.getNegotiatedExtensions()) but there is no way how you could access frame fields other than payload itself.

Proposed Extension API

I have to repeat warning already presented at the start of this blog post – anything mentioned below might be changed without notice. There are some TODO items which will most likely require some modification of presented API, not to mention that RFC drafts of WebSocket Extensions are not final yet. There might be even bigger modification needed – for example, multiplexing draft specifies different frame representation, use of RSV bits is not standardised etc. So please take following as a usable proof of concept and feel free to use them in agile projects.

Firstly, we need to create frame representation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Frame {
public boolean isFin() { .. }
public boolean isRsv1() { .. }
public boolean isRsv2() { .. }
public boolean isRsv3() { .. }
public boolean isMask() { .. }
public byte getOpcode() { .. }
public long getPayloadLength() { .. }
public int getMaskingKey() { .. }
public byte[] getPayloadData() { .. }
public boolean isControlFrame() { .. }
public static Builder builder() { .. }
public static Builder builder(Frame frame) { .. }
public final static class Builder {
public Builder() { .. }
public Builder(Frame frame) { .. }
public Frame build() { .. }
public Builder fin(boolean fin) { .. }
public Builder rsv1(boolean rsv1) { .. }
public Builder rsv2(boolean rsv2) { .. }
public Builder rsv3(boolean rsv3) { .. }
public Builder mask(boolean mask) { .. }
public Builder opcode(byte opcode) { .. }
public Builder payloadLength(long payloadLength) { .. }
public Builder maskingKey(int maskingKey) { .. }
public Builder payloadData(byte[] payloadData) { .. }
}
}

This is pretty much straightforward copy of frame definition mentioned earlier. Frame is designed as immutable, so you cannot change it in any way. One method might be recognised as mutable – getPayloadData() – returns modifiable byte array, but it is always new copy, so the original frame instance remains intact. There is also a Frame.Builder for constructing new Frame instances, notice it can copy existing frame, so creating a new frame with let’s say RSV1 bit set to “1″ is as easy as:

1
Frame newFrame = Frame.builder(originalFrame).rsv1(true).build();

Note that there is only one convenience method: isControlFrame. Other information about frame type etc needs to be evaluated directly from opcode, simply because there might not be enough information to get the correct outcome or the information itself would not be very useful. For example: opcode 0×00 means continuation frame, but you don’t have any chance to get the information about actual type (text or binary) without intercepting data from previous frames. Consider Frame class as as raw as possible representation.isControlFrame can be also gathered from opcode, but it is at least always deterministic and it will be used by most of extension implementations. It is not usual to modify control frames as it might end with half closed connections or unanswered ping messages.

New Extension representation needs to be able to handle extension parameter negotiation and actual processing of incoming and outgoing frames. It also should be compatible with existing javax.websocket.Extension class, since we wan’t to re-use existing registration API and be able to return new extension instance included in response fromSession.getNegotiatedExtensions():List<Extension> call. Consider following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface ExtendedExtension extends Extension {
Frame processIncoming(ExtensionContext context, Frame frame);
Frame processOutgoing(ExtensionContext context, Frame frame);
List onExtensionNegotiation(ExtensionContext context, List requestedParameters);
void onHandshakeResponse(ExtensionContext context, List responseParameters);
void destroy(ExtensionContext context);
interface ExtensionContext {
Map<String, Object> getProperties();
}
}

ExtendedExtension is capable of processing frames and influence parameter values during the handshake. Extension is used on both client and server side and since the negotiation is only place where this fact applies, we needed to somehow differentiate these sides. On server side, only onExtensionNegotiation(..) method is invoked and client side hasonHandshakeResponse(..). Server side method is a must, client side could be somehow solved by implementing ClientEndpointConfig.Configurator#afterResponse(..) or calling Session.getNegotiatedExtenions(), but it won’t be as easy to get this information back to extension instance and even if it was, it won’t be very elegant. Also, you might suggest replacing processIncoming and processOutgoing methods by just oneprocess(Frame) method. That is also possible, but then you might have to assume current direction from frame instance or somehow from ExtenionContext, which is generally not a bad idea, but it resulted it slightly less readable code.

Last but not least is ExtensionContext itself and related lifecycle method. OriginalExtension from javax.websocket is singleton and ExtendedExtension must obey this fact. But it does not meet some requirements we stated previously, like per connection parameter negotiation and of course processing itself will most likely have some connection state. Lifecycle of ExtensionContext is defined as follows: ExtensionContextinstance is created right before onExtensionNegotiation (server side) oronHandshakeResponse (client side) and destroyed after destroy method invocation. Obviously, processIncoming or processOutgoing cannot be called before ExtensionContextis created or after is destroyed. You can think of handshake related methods as @OnOpenand destroy as @OnClose.

For those more familiar with WebSocket protocol: process*(ExtensionContext, Frame) is always invoked with unmasked frame, you don’t need to care about it. On the other side, payload is as it was received from the wire, before any validation (UTF-8 check for text messages). This fact is particularly important when you are modifying text message content, you need to make sure it is properly encoded in relation to other messages, because encoding/decoding process is stateful – remainder after UTF-8 coding is used as input to coding process for next message. If you want just test this feature and save yourself some headaches, don’t modify text message content or try binary messages instead.

Code sample

Let’s say we want to create extension which will encrypt and decrypt first byte of every binary message. Assume we have a key (one byte) and our symmetrical cipher will be XOR. (Just for simplicity (a XOR key XOR key) = a, so encrypt() and decrypt() functions are the same).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class CryptoExtension implements ExtendedExtension {
@Override
public Frame processIncoming(ExtensionContext context, Frame frame) {
return lameCrypt(context, frame);
}
@Override
public Frame processOutgoing(ExtensionContext context, Frame frame) {
return lameCrypt(context, frame);
}
private Frame lameCrypt(ExtensionContext context, Frame frame) {
if(!frame.isControlFrame() && (frame.getOpcode() == 0x02)) {
final byte[] payloadData = frame.getPayloadData();
payloadData[0] ^= (Byte)(context.getProperties().get("key"));
return Frame.builder(frame).payloadData(payloadData).build();
} else {
return frame;
}
}
@Override
public List onExtensionNegotiation(ExtensionContext context,
List requestedParameters) {
init(context);
// no params.
return null;
}
@Override
public void onHandshakeResponse(ExtensionContext context,
List responseParameters) {
init(context);
}
private void init(ExtensionContext context) {
context.getProperties().put("key", (byte)0x55);
}
@Override
public void destroy(ExtensionContext context) {
context.getProperties().clear();
}
@Override
public String getName() {
return "lame-crypto-extension";
}
@Override
public List getParameters() {
// no params.
return null;
}
}

You can see that ExtendedExtension is slightly more complicated that original Extension so the implementation has to be also not as straightforward.. on the other hand, it does something. Sample code above shows possible simplification mentioned earlier (one process method will be enough), but please take this as just sample implementation. Real world case is usually more complicated.

Now when we have our CryptoExtension implemented, we want to use it. There is nothing new compared to standard WebSocket Java API, feel free to skip this part if you are already familiar with it. Only programmatic version will be demonstrated. It is possible to do it for annotated version as well, but it is little bit more complicated on the server side and I want to keep the code as compact as possible.

Client registration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ArrayList extensions = new ArrayList();
extensions.add(new CryptoExtension());
final ClientEndpointConfig clientConfiguration =
ClientEndpointConfig.Builder.create()
.extensions(extensions).build();
WebSocketContainer client = ContainerProvider.getWebSocketContainer();
final Session session = client.connectToServer(new Endpoint() {
@Override
public void onOpen(Session session, EndpointConfig config) {
// ...
}
}, clientConfiguration, URI.create(/* ... */));

Server registration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class CryptoExtensionApplicationConfig implements ServerApplicationConfig {
@Override
public Set getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) {
Set endpointConfigs = new HashSet();
endpointConfigs.add(
ServerEndpointConfig.Builder.create(EchoEndpoint.class, "/echo")
.extensions(Arrays.asList(new CryptoExtension())).build()
);
return endpointConfigs;
}
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) {
// all scanned endpoints will be used.
return scanned;
}
}
public class EchoEndpoint extends Endpoint {
@Override
public void onOpen(Session session, EndpointConfig config) {
// ...
}
}

CryptoExtensionApplicationConfig will be found by servlets scanning mechanism and automatically used for application configuration, no need to add anything (or even have)web.xml.

Per Message Deflate Extension

The original goal of whole extension support was to implement Permessage extension as defined in draft-ietf-hybi-permessage-compression-15 and we were able to achieve that goal. Well, not completely, current implementation ignores parameters. But it seems like it does not matter much, it was tested with Chrome and it works fine. Also it passes newest version of Autobahn test suite, which includes tests for this extension.

  1. PerMessageDeflateExtension.java (compatible with draft-ietf-hybi-permessage-compression-15, autobahn test suite)
  2. XWebKitDeflateExtension.java (compatible with Chrome and Firefox – same as previous, just different extension name)
  3. PerMessageDeflateTest.java

TODO

There are some things which needs to be improved or specified to make this reliable and suitable for real world use. It might not seem as big feature, but it enables lots of use cases not defined in original specification and some of them are clashing little bit, so for now, I kept everything as it was when you are not using extended extensions.

Everything mentioned in this article is already available in Tyrus 1.4-SNAPSHOT and will be part of 1.4 release.

  • Extension / Frame Validation
  • Frame representation – frame types
  • Frame representation – payload representation
  • Frame representation – masking – remove? (current state: container responsibility)
  • Exception handling – processIncoming and processOutgoing methods (current state: exceptions are logged)
  • Exception handling – onExtensionNegotiation
  • Possibility to reject negotiation extension in onExtensionNegotiation (based on extension params)
  • Extension ordering (current state: handshake response header order)
  • Extension resource validation (two extensions using same RSV bit(s) cannot be negotiated)
  • PerMessageDeflate – parameters
  • MultiplexingExtension – implement when (if) ready

Conclusion

There is still lots of decisions to be made and things to do, but it seems like we can implement usable extensions which are supported by newer versions of browsers and containers. PerMessageDeflate extension is nice example of handy feature which can save significant resources.

Links

Wednesday Dec 18, 2013

Want to work on Tyrus? We are hiring!

Tyrus project is looking for another contributor! If you want to became part of the team which is working on WebSocket API for Java (JSR 356) Reference implementation and related projects and products, this is the time to update your CV and share it with us. This job position is based in Prague, Czech Republic.

Formal job offer can be found on linked in.

If you are interested and have any questions, feel free to contact me directly via this blog or via pavel.bucek at oracle.com.

About

Pavel Bucek-Oracle

Search

Categories
Archives
« July 2015
SunMonTueWedThuFriSat
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
       
Today