mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-04 01:25:43 -05:00
Move unused forked code to external directory (#1192)
* Move unused forked code to external directory * Fix SLN build errors * Add back resource provider core since it's referenced by main resource provider project * Update PackageProjects step of pipeline
This commit is contained in:
32
external/Microsoft.SqlTools.Hosting.v2/Protocol/EventContext.cs
vendored
Normal file
32
external/Microsoft.SqlTools.Hosting.v2/Protocol/EventContext.cs
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.SqlTools.Hosting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides context for a received event so that handlers
|
||||
/// can write events back to the channel.
|
||||
/// </summary>
|
||||
public class EventContext : IEventSender
|
||||
{
|
||||
internal readonly BlockingCollection<Message> messageQueue;
|
||||
|
||||
public EventContext(BlockingCollection<Message> outgoingMessageQueue)
|
||||
{
|
||||
// TODO: Either 1) make this constructor internal and provide a test framework for validating
|
||||
// or 2) extract an interface for eventcontext to allow users to mock
|
||||
messageQueue = outgoingMessageQueue;
|
||||
}
|
||||
|
||||
public void SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
||||
{
|
||||
messageQueue.Add(Message.CreateEvent(eventType, eventParams));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
external/Microsoft.SqlTools.Hosting.v2/Protocol/Exceptions.cs
vendored
Normal file
35
external/Microsoft.SqlTools.Hosting.v2/Protocol/Exceptions.cs
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using Microsoft.SqlTools.Hosting.v2;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when parsing a message from input stream fails.
|
||||
/// </summary>
|
||||
public class MessageParseException : Exception
|
||||
{
|
||||
public string OriginalMessageText { get; }
|
||||
|
||||
public MessageParseException(string originalMessageText, string errorMessage, params object[] errorMessageArgs)
|
||||
: base(string.Format(errorMessage, errorMessageArgs))
|
||||
{
|
||||
OriginalMessageText = originalMessageText;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown when a handler for a given request/event method does not exist
|
||||
/// </summary>
|
||||
public class MethodHandlerDoesNotExistException : Exception
|
||||
{
|
||||
public MethodHandlerDoesNotExistException(MessageType type, string method)
|
||||
: base(SR.HostingMethodHandlerDoesNotExist(type.ToString(), method))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
23
external/Microsoft.SqlTools.Hosting.v2/Protocol/IEventSender.cs
vendored
Normal file
23
external/Microsoft.SqlTools.Hosting.v2/Protocol/IEventSender.cs
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.Hosting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for objects that can send events via the JSON RPC channel
|
||||
/// </summary>
|
||||
public interface IEventSender
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends an event over the JSON RPC channel
|
||||
/// </summary>
|
||||
/// <param name="eventType">Configuration of the event to send</param>
|
||||
/// <param name="eventParams">Parameters for the event to send</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the event, defined in <paramref name="eventType"/></typeparam>
|
||||
void SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams);
|
||||
}
|
||||
}
|
||||
28
external/Microsoft.SqlTools.Hosting.v2/Protocol/IJsonRpcHost.cs
vendored
Normal file
28
external/Microsoft.SqlTools.Hosting.v2/Protocol/IJsonRpcHost.cs
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a JSON RPC host
|
||||
/// </summary>
|
||||
public interface IJsonRpcHost : IEventSender, IRequestSender, IMessageDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts the JSON RPC host
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Stops the JSON RPC host
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the JSON RPC host to exit
|
||||
/// </summary>
|
||||
void WaitForExit();
|
||||
}
|
||||
}
|
||||
72
external/Microsoft.SqlTools.Hosting.v2/Protocol/IMessageDispatcher.cs
vendored
Normal file
72
external/Microsoft.SqlTools.Hosting.v2/Protocol/IMessageDispatcher.cs
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for objects that can will handle messages. The methods exposed via this interface
|
||||
/// allow users to what to do when a specific message is received.
|
||||
/// </summary>
|
||||
public interface IMessageDispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the function to run when a request message of a specific
|
||||
/// <paramref name="requestType"/> is received
|
||||
/// </summary>
|
||||
/// <param name="requestType">Configuration of the request message <paramref name="requestHandler"/> will handle</param>
|
||||
/// <param name="requestHandler">What to do when a request message of <paramref name="requestType"/> is received</param>
|
||||
/// <param name="overrideExisting">If <c>true</c>, any existing handler will be replaced with this one</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the request, defined by <paramref name="requestType"/></typeparam>
|
||||
/// <typeparam name="TResult">Type of the response to the request, defined by <paramref name="requestType"/></typeparam>
|
||||
void SetAsyncRequestHandler<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
Func<TParams, RequestContext<TResult>, Task> requestHandler,
|
||||
bool overrideExisting = false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the function to run when a request message of a specific
|
||||
/// <paramref name="requestType"/> is received
|
||||
/// </summary>
|
||||
/// <param name="requestType">Configuration of the request message <paramref name="requestHandler"/> will handle</param>
|
||||
/// <param name="requestHandler">What to do when a request message of <paramref name="requestType"/> is received</param>
|
||||
/// <param name="overrideExisting">If <c>true</c>, any existing handler will be replaced with this one</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the request, defined by <paramref name="requestType"/></typeparam>
|
||||
/// <typeparam name="TResult">Type of the response to the request, defined by <paramref name="requestType"/></typeparam>
|
||||
void SetRequestHandler<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
Action<TParams, RequestContext<TResult>> requestHandler,
|
||||
bool overrideExisting = false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the function to run when an event message of a specific configurat
|
||||
/// <paramref name="eventType"/> is received
|
||||
/// </summary>
|
||||
/// <param name="eventType">Configuration of the event message <paramref name="eventHandler"/> will handle</param>
|
||||
/// <param name="eventHandler">What to do when an event message of <paramref name="eventType"/> is received</param>
|
||||
/// <param name="overrideExisting">If <c>true</c>, any existing handler will be replaced with this one</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the event</typeparam>
|
||||
void SetAsyncEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
Func<TParams, EventContext, Task> eventHandler,
|
||||
bool overrideExisting = false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the function to run when an event message of a specific
|
||||
/// <paramref name="eventType"/> is received
|
||||
/// </summary>
|
||||
/// <param name="eventType">Configuration of the event message <paramref name="eventHandler"/> will handle</param>
|
||||
/// <param name="eventHandler">What to do when an event message of <paramref name="eventType"/> is received</param>
|
||||
/// <param name="overrideExisting">If <c>true</c>, any existing handler will be replaced with this one</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the event</typeparam>
|
||||
void SetEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
Action<TParams, EventContext> eventHandler,
|
||||
bool overrideExisting = false);
|
||||
}
|
||||
}
|
||||
27
external/Microsoft.SqlTools.Hosting.v2/Protocol/IRequestSender.cs
vendored
Normal file
27
external/Microsoft.SqlTools.Hosting.v2/Protocol/IRequestSender.cs
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for objects that can requests via the JSON RPC channel
|
||||
/// </summary>
|
||||
public interface IRequestSender
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a request over the JSON RPC channel. It will wait for a response to the message
|
||||
/// before completing.
|
||||
/// </summary>
|
||||
/// <param name="requestType">Configuration of the request to send</param>
|
||||
/// <param name="requestParams">Parameters for the request to send</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the request, defined by <paramref name="requestType"/></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <returns></returns>
|
||||
Task<TResult> SendRequest<TParams, TResult>(RequestType<TParams, TResult> requestType, TParams requestParams);
|
||||
}
|
||||
}
|
||||
448
external/Microsoft.SqlTools.Hosting.v2/Protocol/JsonRpcHost.cs
vendored
Normal file
448
external/Microsoft.SqlTools.Hosting.v2/Protocol/JsonRpcHost.cs
vendored
Normal file
@@ -0,0 +1,448 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Channels;
|
||||
using Microsoft.SqlTools.Hosting.Contracts;
|
||||
using Microsoft.SqlTools.Hosting.Contracts.Internal;
|
||||
using Microsoft.SqlTools.Hosting.Utility;
|
||||
using Microsoft.SqlTools.Hosting.v2;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
public class JsonRpcHost : IJsonRpcHost
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
internal readonly CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
private readonly CancellationToken consumeInputCancellationToken;
|
||||
private readonly CancellationToken consumeOutputCancellationToken;
|
||||
|
||||
internal readonly BlockingCollection<Message> outputQueue;
|
||||
internal readonly Dictionary<string, Func<Message, Task>> eventHandlers;
|
||||
internal readonly Dictionary<string, Func<Message, Task>> requestHandlers;
|
||||
internal readonly ConcurrentDictionary<string, TaskCompletionSource<Message>> pendingRequests;
|
||||
internal readonly ChannelBase protocolChannel;
|
||||
|
||||
internal Task consumeInputTask;
|
||||
internal Task consumeOutputTask;
|
||||
private bool isStarted;
|
||||
|
||||
#endregion
|
||||
|
||||
public JsonRpcHost(ChannelBase channel)
|
||||
{
|
||||
Validate.IsNotNull(nameof(channel), channel);
|
||||
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
consumeInputCancellationToken = cancellationTokenSource.Token;
|
||||
consumeOutputCancellationToken = cancellationTokenSource.Token;
|
||||
outputQueue = new BlockingCollection<Message>(new ConcurrentQueue<Message>());
|
||||
protocolChannel = channel;
|
||||
|
||||
eventHandlers = new Dictionary<string, Func<Message, Task>>();
|
||||
requestHandlers = new Dictionary<string, Func<Message, Task>>();
|
||||
pendingRequests = new ConcurrentDictionary<string, TaskCompletionSource<Message>>();
|
||||
}
|
||||
|
||||
#region Start/Stop Methods
|
||||
|
||||
/// <summary>
|
||||
/// Starts the JSON RPC host using the protocol channel that was provided
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
// If we've already started, we can't start up again
|
||||
if (isStarted)
|
||||
{
|
||||
throw new InvalidOperationException(SR.HostingJsonRpcHostAlreadyStarted);
|
||||
}
|
||||
|
||||
// Make sure no other calls try to start the endpoint during startup
|
||||
isStarted = true;
|
||||
|
||||
// Initialize the protocol channel
|
||||
protocolChannel.Start();
|
||||
protocolChannel.WaitForConnection().Wait();
|
||||
|
||||
// Start the input and output consumption threads
|
||||
consumeInputTask = ConsumeInput();
|
||||
consumeOutputTask = ConsumeOutput();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the JSON RPC host and the underlying protocol channel
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
// If we haven't started, we can't stop
|
||||
if (!isStarted)
|
||||
{
|
||||
throw new InvalidOperationException(SR.HostingJsonRpcHostNotStarted);
|
||||
}
|
||||
|
||||
// Make sure no future calls try to stop the endpoint during shutdown
|
||||
isStarted = false;
|
||||
|
||||
// Shutdown the host
|
||||
cancellationTokenSource.Cancel();
|
||||
protocolChannel.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for input and output threads to naturally exit
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the host has not started</exception>
|
||||
public void WaitForExit()
|
||||
{
|
||||
// If we haven't started everything, we can't wait for exit
|
||||
if (!isStarted)
|
||||
{
|
||||
throw new InvalidOperationException(SR.HostingJsonRpcHostNotStarted);
|
||||
}
|
||||
|
||||
// Join the input and output threads to this thread
|
||||
Task.WaitAll(consumeInputTask, consumeOutputTask);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Sends an event, independent of any request
|
||||
/// </summary>
|
||||
/// <typeparam name="TParams">Event parameter type</typeparam>
|
||||
/// <param name="eventType">Type of event being sent</param>
|
||||
/// <param name="eventParams">Event parameters being sent</param>
|
||||
/// <returns>Task that tracks completion of the send operation.</returns>
|
||||
public void SendEvent<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
TParams eventParams)
|
||||
{
|
||||
if (!protocolChannel.IsConnected)
|
||||
{
|
||||
throw new InvalidOperationException("SendEvent called when ProtocolChannel was not yet connected");
|
||||
}
|
||||
|
||||
// Create a message from the event provided
|
||||
Message message = Message.CreateEvent(eventType, eventParams);
|
||||
outputQueue.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a request, independent of any request
|
||||
/// </summary>
|
||||
/// <param name="requestType">Configuration of the request that is being sent</param>
|
||||
/// <param name="requestParams">Contents of the request</param>
|
||||
/// <typeparam name="TParams">Type of the message contents</typeparam>
|
||||
/// <typeparam name="TResult">Type of the contents of the expected result of the request</typeparam>
|
||||
/// <returns>Task that is completed when the </returns>
|
||||
/// TODO: This doesn't properly handle error responses scenarios.
|
||||
public async Task<TResult> SendRequest<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
TParams requestParams)
|
||||
{
|
||||
if (!protocolChannel.IsConnected)
|
||||
{
|
||||
throw new InvalidOperationException("SendRequest called when ProtocolChannel was not yet connected");
|
||||
}
|
||||
|
||||
// Add a task completion source for the request's response
|
||||
string messageId = Guid.NewGuid().ToString();
|
||||
TaskCompletionSource<Message> responseTask = new TaskCompletionSource<Message>();
|
||||
pendingRequests.TryAdd(messageId, responseTask);
|
||||
|
||||
// Send the request
|
||||
outputQueue.Add(Message.CreateRequest(requestType, messageId, requestParams));
|
||||
|
||||
// Wait for the response
|
||||
Message responseMessage = await responseTask.Task;
|
||||
|
||||
return responseMessage.GetTypedContents<TResult>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for an event with a given configuration
|
||||
/// </summary>
|
||||
/// <param name="eventType">Configuration of the event</param>
|
||||
/// <param name="eventHandler">Function for handling the event</param>
|
||||
/// <param name="overrideExisting">Whether or not to override any existing event handler for this method</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the event</typeparam>
|
||||
public void SetAsyncEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
Func<TParams, EventContext, Task> eventHandler,
|
||||
bool overrideExisting = false)
|
||||
{
|
||||
Validate.IsNotNull(nameof(eventType), eventType);
|
||||
Validate.IsNotNull(nameof(eventHandler), eventHandler);
|
||||
|
||||
if (overrideExisting)
|
||||
{
|
||||
// Remove the existing handler so a new one can be set
|
||||
eventHandlers.Remove(eventType.MethodName);
|
||||
}
|
||||
|
||||
Func<Message, Task> handler = eventMessage =>
|
||||
eventHandler(eventMessage.GetTypedContents<TParams>(), new EventContext(outputQueue));
|
||||
|
||||
eventHandlers.Add(eventType.MethodName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Func based that wraps the action in a task and calls the Func-based overload
|
||||
/// </summary>
|
||||
/// <param name="eventType">Configuration of the event</param>
|
||||
/// <param name="eventHandler">Function for handling the event</param>
|
||||
/// <param name="overrideExisting">Whether or not to override any existing event handler for this method</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the event</typeparam>
|
||||
public void SetEventHandler<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
Action<TParams, EventContext> eventHandler,
|
||||
bool overrideExisting = false)
|
||||
{
|
||||
Validate.IsNotNull(nameof(eventHandler), eventHandler);
|
||||
Func<TParams, EventContext, Task> eventFunc = (p, e) => Task.Run(() => eventHandler(p, e));
|
||||
SetAsyncEventHandler(eventType, eventFunc, overrideExisting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the handler for a request with a given configuration
|
||||
/// </summary>
|
||||
/// <param name="requestType">Configuration of the request</param>
|
||||
/// <param name="requestHandler">Function for handling the request</param>
|
||||
/// <param name="overrideExisting">Whether or not to override any existing request handler for this method</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the request</typeparam>
|
||||
/// <typeparam name="TResult">Type of the parameters for the response</typeparam>
|
||||
public void SetAsyncRequestHandler<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
Func<TParams, RequestContext<TResult>, Task> requestHandler,
|
||||
bool overrideExisting = false)
|
||||
{
|
||||
Validate.IsNotNull(nameof(requestType), requestType);
|
||||
Validate.IsNotNull(nameof(requestHandler), requestHandler);
|
||||
|
||||
if (overrideExisting)
|
||||
{
|
||||
// Remove the existing handler so a new one can be set
|
||||
requestHandlers.Remove(requestType.MethodName);
|
||||
}
|
||||
|
||||
// Setup the wrapper around the handler
|
||||
Func<Message, Task> handler = requestMessage =>
|
||||
requestHandler(requestMessage.GetTypedContents<TParams>(), new RequestContext<TResult>(requestMessage, outputQueue));
|
||||
|
||||
requestHandlers.Add(requestType.MethodName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Func based that wraps the action in a task and calls the Func-based overload
|
||||
/// </summary>
|
||||
/// /// <param name="requestType">Configuration of the request</param>
|
||||
/// <param name="requestHandler">Function for handling the request</param>
|
||||
/// <param name="overrideExisting">Whether or not to override any existing request handler for this method</param>
|
||||
/// <typeparam name="TParams">Type of the parameters for the request</typeparam>
|
||||
/// <typeparam name="TResult">Type of the parameters for the response</typeparam>
|
||||
public void SetRequestHandler<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
Action<TParams, RequestContext<TResult>> requestHandler,
|
||||
bool overrideExisting = false)
|
||||
{
|
||||
Validate.IsNotNull(nameof(requestHandler), requestHandler);
|
||||
Func<TParams, RequestContext<TResult>, Task> requestFunc = (p, e) => Task.Run(() => requestHandler(p, e));
|
||||
SetAsyncRequestHandler(requestType, requestFunc, overrideExisting);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Message Processing Tasks
|
||||
|
||||
internal Task ConsumeInput()
|
||||
{
|
||||
return Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (!consumeInputCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Message incomingMessage;
|
||||
try
|
||||
{
|
||||
// Read message from the input channel
|
||||
incomingMessage = await protocolChannel.MessageReader.ReadMessage();
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
// The stream has ended, end the input message loop
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Log the error and send an error event to the client
|
||||
string message = $"Exception occurred while receiving input message: {e.Message}";
|
||||
Logger.Write(TraceEventType.Error, message);
|
||||
|
||||
// TODO: Add event to output queue, and unit test it
|
||||
|
||||
// Continue the loop
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verbose logging
|
||||
string logMessage =
|
||||
$"Received message with Id[{incomingMessage.Id}] of type[{incomingMessage.MessageType}] and method[{incomingMessage.Method}]";
|
||||
Logger.Write(TraceEventType.Verbose, logMessage);
|
||||
|
||||
// Process the message
|
||||
try
|
||||
{
|
||||
await DispatchMessage(incomingMessage);
|
||||
}
|
||||
catch (MethodHandlerDoesNotExistException)
|
||||
{
|
||||
// Method could not be handled, if the message was a request, send an error back to the client
|
||||
// TODO: Localize
|
||||
string mnfLogMessage =
|
||||
$"Failed to find method handler for type[{incomingMessage.MessageType}] and method[{incomingMessage.Method}]";
|
||||
Logger.Write(TraceEventType.Warning, mnfLogMessage);
|
||||
|
||||
if (incomingMessage.MessageType == MessageType.Request)
|
||||
{
|
||||
// TODO: Localize
|
||||
Error mnfError = new Error {Code = -32601, Message = "Method not found"};
|
||||
Message errorMessage = Message.CreateResponseError(incomingMessage.Id, mnfError);
|
||||
outputQueue.Add(errorMessage, consumeInputCancellationToken);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// General errors should be logged but not halt the processing loop
|
||||
string geLogMessage =
|
||||
$"Exception thrown when handling message of type[{incomingMessage.MessageType}] and method[{incomingMessage.Method}]: {e}";
|
||||
Logger.Write(TraceEventType.Error, geLogMessage);
|
||||
// TODO: Should we be returning a response for failing requests?
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Write(TraceEventType.Warning, "Exiting consume input loop!");
|
||||
}, consumeOutputCancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
|
||||
}
|
||||
|
||||
internal Task ConsumeOutput()
|
||||
{
|
||||
return Task.Factory.StartNew(async () =>
|
||||
{
|
||||
while (!consumeOutputCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Message outgoingMessage;
|
||||
try
|
||||
{
|
||||
// Read message from the output queue
|
||||
outgoingMessage = outputQueue.Take(consumeOutputCancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Cancelled during taking, end the loop
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// If we hit an exception here, it is unrecoverable
|
||||
string message = string.Format("Unexpected occurred while receiving output message: {0}", e.Message);
|
||||
Logger.Write(TraceEventType.Error, message);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Send the message
|
||||
string logMessage = string.Format("Sending message of type[{0}] and method[{1}]",
|
||||
outgoingMessage.MessageType, outgoingMessage.Method);
|
||||
Logger.Write(TraceEventType.Verbose, logMessage);
|
||||
|
||||
await protocolChannel.MessageWriter.WriteMessage(outgoingMessage);
|
||||
}
|
||||
Logger.Write(TraceEventType.Warning, "Exiting consume output loop!");
|
||||
}, consumeOutputCancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
|
||||
}
|
||||
|
||||
internal async Task DispatchMessage(Message messageToDispatch)
|
||||
{
|
||||
Task handlerToAwait = null;
|
||||
|
||||
switch (messageToDispatch.MessageType)
|
||||
{
|
||||
case MessageType.Request:
|
||||
Func<Message, Task> requestHandler;
|
||||
if (requestHandlers.TryGetValue(messageToDispatch.Method, out requestHandler))
|
||||
{
|
||||
handlerToAwait = requestHandler(messageToDispatch);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MethodHandlerDoesNotExistException(MessageType.Request, messageToDispatch.Method);
|
||||
}
|
||||
break;
|
||||
case MessageType.Response:
|
||||
TaskCompletionSource<Message> requestTask;
|
||||
if (pendingRequests.TryRemove(messageToDispatch.Id, out requestTask))
|
||||
{
|
||||
requestTask.SetResult(messageToDispatch);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MethodHandlerDoesNotExistException(MessageType.Response, "response");
|
||||
}
|
||||
case MessageType.Event:
|
||||
Func<Message, Task> eventHandler;
|
||||
if (eventHandlers.TryGetValue(messageToDispatch.Method, out eventHandler))
|
||||
{
|
||||
handlerToAwait = eventHandler(messageToDispatch);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MethodHandlerDoesNotExistException(MessageType.Event, messageToDispatch.Method);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// TODO: This case isn't handled properly
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip processing if there isn't anything to do
|
||||
if (handlerToAwait == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the handler
|
||||
try
|
||||
{
|
||||
await handlerToAwait;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Some tasks may be cancelled due to legitimate
|
||||
// timeouts so don't let those exceptions go higher.
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
if (!(e.InnerExceptions[0] is TaskCanceledException))
|
||||
{
|
||||
// Cancelled tasks aren't a problem, so rethrow
|
||||
// anything that isn't a TaskCanceledException
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
252
external/Microsoft.SqlTools.Hosting.v2/Protocol/Message.cs
vendored
Normal file
252
external/Microsoft.SqlTools.Hosting.v2/Protocol/Message.cs
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.SqlTools.Hosting.Contracts;
|
||||
using Microsoft.SqlTools.Hosting.Contracts.Internal;
|
||||
using Microsoft.SqlTools.Hosting.v2;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all possible message types.
|
||||
/// </summary>
|
||||
public enum MessageType
|
||||
{
|
||||
Request,
|
||||
Response,
|
||||
ResponseError,
|
||||
Event
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Representation for a JSON RPC message. Provides logic for converting back and forth from
|
||||
/// string
|
||||
/// </summary>
|
||||
[DebuggerDisplay("MessageType = {MessageType.ToString()}, Method = {Method}, Id = {Id}")]
|
||||
public class Message
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string JsonRpcVersion = "2.0";
|
||||
|
||||
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
};
|
||||
private static readonly JsonSerializer ContentSerializer = JsonSerializer.Create(JsonSerializerSettings);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction
|
||||
|
||||
private Message(MessageType messageType, JToken contents)
|
||||
{
|
||||
MessageType = messageType;
|
||||
Contents = contents;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a message with a Request type
|
||||
/// </summary>
|
||||
/// <param name="requestType">Configuration for the request</param>
|
||||
/// <param name="id">ID of the message</param>
|
||||
/// <param name="contents">Contents of the message</param>
|
||||
/// <typeparam name="TParams">Type of the contents of the message</typeparam>
|
||||
/// <typeparam name="TResult">Type of the contents of the results to this request</typeparam>
|
||||
/// <returns>Message with a Request type</returns>
|
||||
public static Message CreateRequest<TParams, TResult>(
|
||||
RequestType<TParams, TResult> requestType,
|
||||
string id,
|
||||
TParams contents)
|
||||
{
|
||||
JToken contentsToken = contents == null ? null : JToken.FromObject(contents, ContentSerializer);
|
||||
return new Message(MessageType.Request, contentsToken)
|
||||
{
|
||||
Id = id,
|
||||
Method = requestType.MethodName
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a message with a Response type.
|
||||
/// </summary>
|
||||
/// <param name="id">The sequence ID of the original request.</param>
|
||||
/// <param name="contents">The contents of the response.</param>
|
||||
/// <returns>A message with a Response type.</returns>
|
||||
public static Message CreateResponse<TParams>(
|
||||
string id,
|
||||
TParams contents)
|
||||
{
|
||||
JToken contentsToken = contents == null ? null : JToken.FromObject(contents, ContentSerializer);
|
||||
return new Message(MessageType.Response, contentsToken)
|
||||
{
|
||||
Id = id
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a message with a Response type and error details.
|
||||
/// </summary>
|
||||
/// <param name="id">The sequence ID of the original request.</param>
|
||||
/// <param name="error">The error details of the response.</param>
|
||||
/// <returns>A message with a Response type and error details.</returns>
|
||||
public static Message CreateResponseError(
|
||||
string id,
|
||||
Error error)
|
||||
{
|
||||
JToken errorToken = error == null ? null : JToken.FromObject(error, ContentSerializer);
|
||||
return new Message(MessageType.ResponseError, errorToken)
|
||||
{
|
||||
Id = id
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a message with an Event type.
|
||||
/// </summary>
|
||||
/// <param name="eventType">Configuration for the event message</param>
|
||||
/// <param name="contents">The contents of the event.</param>
|
||||
/// <typeparam name="TParams"></typeparam>
|
||||
/// <returns>A message with an Event type</returns>
|
||||
public static Message CreateEvent<TParams>(
|
||||
EventType<TParams> eventType,
|
||||
TParams contents)
|
||||
{
|
||||
JToken contentsToken = contents == null ? null : JToken.FromObject(contents, ContentSerializer);
|
||||
return new Message(MessageType.Event, contentsToken)
|
||||
{
|
||||
Method = eventType.MethodName
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message type.
|
||||
/// </summary>
|
||||
public MessageType MessageType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message's sequence ID.
|
||||
/// </summary>
|
||||
public string Id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message's method/command name.
|
||||
/// </summary>
|
||||
public string Method { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a JToken containing the contents of the message.
|
||||
/// </summary>
|
||||
public JToken Contents { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialization/Deserialization
|
||||
|
||||
public static Message Deserialize(string jsonString)
|
||||
{
|
||||
// Deserialize the object from the JSON into an intermediate object
|
||||
JObject messageObject = JObject.Parse(jsonString);
|
||||
JToken token;
|
||||
|
||||
// Ensure there's a JSON RPC version or else it's invalid
|
||||
if (!messageObject.TryGetValue("jsonrpc", out token) || token.Value<string>() != JsonRpcVersion)
|
||||
{
|
||||
throw new MessageParseException(null, SR.HostingJsonRpcVersionMissing);
|
||||
}
|
||||
|
||||
if (messageObject.TryGetValue("id", out token))
|
||||
{
|
||||
// Message with ID is a Request or Response
|
||||
string messageId = token.ToString();
|
||||
|
||||
if (messageObject.TryGetValue("result", out token))
|
||||
{
|
||||
return new Message(MessageType.Response, token) {Id = messageId};
|
||||
}
|
||||
if (messageObject.TryGetValue("error", out token))
|
||||
{
|
||||
return new Message(MessageType.ResponseError, token) {Id = messageId};
|
||||
}
|
||||
|
||||
// Message without result/error is a Request
|
||||
JToken messageParams;
|
||||
messageObject.TryGetValue("params", out messageParams);
|
||||
if (!messageObject.TryGetValue("method", out token))
|
||||
{
|
||||
throw new MessageParseException(null, SR.HostingMessageMissingMethod);
|
||||
}
|
||||
|
||||
return new Message(MessageType.Request, messageParams) {Id = messageId, Method = token.ToString()};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Messages without an id are events
|
||||
JToken messageParams;
|
||||
messageObject.TryGetValue("params", out messageParams);
|
||||
|
||||
if (!messageObject.TryGetValue("method", out token))
|
||||
{
|
||||
throw new MessageParseException(null, SR.HostingMessageMissingMethod);
|
||||
}
|
||||
|
||||
return new Message(MessageType.Event, messageParams) {Method = token.ToString()};
|
||||
}
|
||||
}
|
||||
|
||||
public string Serialize()
|
||||
{
|
||||
JObject messageObject = new JObject
|
||||
{
|
||||
{"jsonrpc", JToken.FromObject(JsonRpcVersion)}
|
||||
};
|
||||
|
||||
switch (MessageType)
|
||||
{
|
||||
case MessageType.Request:
|
||||
messageObject.Add("id", JToken.FromObject(Id));
|
||||
messageObject.Add("method", Method);
|
||||
messageObject.Add("params", Contents);
|
||||
break;
|
||||
case MessageType.Event:
|
||||
messageObject.Add("method", Method);
|
||||
messageObject.Add("params", Contents);
|
||||
break;
|
||||
case MessageType.Response:
|
||||
messageObject.Add("id", JToken.FromObject(Id));
|
||||
messageObject.Add("result", Contents);
|
||||
break;
|
||||
case MessageType.ResponseError:
|
||||
messageObject.Add("id", JToken.FromObject(Id));
|
||||
messageObject.Add("error", Contents);
|
||||
break;
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(messageObject);
|
||||
}
|
||||
|
||||
public TContents GetTypedContents<TContents>()
|
||||
{
|
||||
TContents typedContents = default(TContents);
|
||||
if (Contents != null)
|
||||
{
|
||||
typedContents = Contents.ToObject<TContents>();
|
||||
}
|
||||
|
||||
return typedContents;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
258
external/Microsoft.SqlTools.Hosting.v2/Protocol/MessageReader.cs
vendored
Normal file
258
external/Microsoft.SqlTools.Hosting.v2/Protocol/MessageReader.cs
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Utility;
|
||||
using Microsoft.SqlTools.Hosting.v2;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
public class MessageReader
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
public const int DefaultBufferSize = 8192;
|
||||
private const double BufferResizeTrigger = 0.25;
|
||||
|
||||
private const int CR = 0x0D;
|
||||
private const int LF = 0x0A;
|
||||
private static readonly string[] NewLineDelimiters = { Environment.NewLine };
|
||||
|
||||
private readonly Stream inputStream;
|
||||
|
||||
private bool needsMoreData = true;
|
||||
private int readOffset;
|
||||
private int bufferEndOffset;
|
||||
|
||||
private byte[] messageBuffer;
|
||||
|
||||
private int expectedContentLength;
|
||||
private Dictionary<string, string> messageHeaders;
|
||||
|
||||
internal enum ReadState
|
||||
{
|
||||
Headers,
|
||||
Content
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public MessageReader(Stream inputStream, Encoding messageEncoding = null)
|
||||
{
|
||||
Validate.IsNotNull("streamReader", inputStream);
|
||||
|
||||
this.inputStream = inputStream;
|
||||
MessageEncoding = messageEncoding ?? Encoding.UTF8;
|
||||
|
||||
messageBuffer = new byte[DefaultBufferSize];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Testable Properties
|
||||
|
||||
internal byte[] MessageBuffer
|
||||
{
|
||||
get => messageBuffer;
|
||||
set => messageBuffer = value;
|
||||
}
|
||||
|
||||
internal ReadState CurrentState { get; private set; }
|
||||
|
||||
internal Encoding MessageEncoding { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public virtual async Task<Message> ReadMessage()
|
||||
{
|
||||
string messageContent = null;
|
||||
|
||||
// Do we need to read more data or can we process the existing buffer?
|
||||
while (!needsMoreData || await ReadNextChunk())
|
||||
{
|
||||
// Clear the flag since we should have what we need now
|
||||
needsMoreData = false;
|
||||
|
||||
// Do we need to look for message headers?
|
||||
if (CurrentState == ReadState.Headers && !TryReadMessageHeaders())
|
||||
{
|
||||
// If we don't have enough data to read headers yet, keep reading
|
||||
needsMoreData = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do we need to look for message content?
|
||||
if (CurrentState == ReadState.Content && !TryReadMessageContent(out messageContent))
|
||||
{
|
||||
// If we don't have enough data yet to construct the content, keep reading
|
||||
needsMoreData = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We've read a message now, break out of the loop
|
||||
break;
|
||||
}
|
||||
|
||||
// Now that we have a message, reset the buffer's state
|
||||
ShiftBufferBytesAndShrink(readOffset);
|
||||
|
||||
// Return the parsed message
|
||||
return Message.Deserialize(messageContent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task<bool> ReadNextChunk()
|
||||
{
|
||||
// Do we need to resize the buffer? See if less than 1/4 of the space is left.
|
||||
if ((double)(messageBuffer.Length - bufferEndOffset) / messageBuffer.Length < BufferResizeTrigger)
|
||||
{
|
||||
// Double the size of the buffer
|
||||
Array.Resize(ref messageBuffer, messageBuffer.Length * 2);
|
||||
}
|
||||
|
||||
// Read the next chunk into the message buffer
|
||||
int readLength =
|
||||
await inputStream.ReadAsync(messageBuffer, bufferEndOffset, messageBuffer.Length - bufferEndOffset);
|
||||
|
||||
bufferEndOffset += readLength;
|
||||
|
||||
if (readLength == 0)
|
||||
{
|
||||
// If ReadAsync returns 0 then it means that the stream was
|
||||
// closed unexpectedly (usually due to the client application
|
||||
// ending suddenly). For now, just terminate the language
|
||||
// server immediately.
|
||||
throw new EndOfStreamException(SR.HostingUnexpectedEndOfStream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadMessageHeaders()
|
||||
{
|
||||
int scanOffset = readOffset;
|
||||
|
||||
// Scan for the final double-newline that marks the end of the header lines
|
||||
while (scanOffset + 3 < bufferEndOffset &&
|
||||
(messageBuffer[scanOffset] != CR ||
|
||||
messageBuffer[scanOffset + 1] != LF ||
|
||||
messageBuffer[scanOffset + 2] != CR ||
|
||||
messageBuffer[scanOffset + 3] != LF))
|
||||
{
|
||||
scanOffset++;
|
||||
}
|
||||
|
||||
// Make sure we haven't reached the end of the buffer without finding a separator (e.g CRLFCRLF)
|
||||
if (scanOffset + 3 >= bufferEndOffset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert the header block into a array of lines
|
||||
var headers = Encoding.ASCII.GetString(messageBuffer, readOffset, scanOffset)
|
||||
.Split(NewLineDelimiters, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
try
|
||||
{
|
||||
// Read each header and store it in the dictionary
|
||||
messageHeaders = new Dictionary<string, string>();
|
||||
foreach (var header in headers)
|
||||
{
|
||||
int currentLength = header.IndexOf(':');
|
||||
if (currentLength == -1)
|
||||
{
|
||||
throw new ArgumentException(SR.HostingHeaderMissingColon);
|
||||
}
|
||||
|
||||
var key = header.Substring(0, currentLength);
|
||||
var value = header.Substring(currentLength + 1).Trim();
|
||||
messageHeaders[key] = value;
|
||||
}
|
||||
|
||||
// Parse out the content length as an int
|
||||
string contentLengthString;
|
||||
if (!messageHeaders.TryGetValue("Content-Length", out contentLengthString))
|
||||
{
|
||||
throw new MessageParseException("", SR.HostingHeaderMissingContentLengthHeader);
|
||||
}
|
||||
|
||||
// Parse the content length to an integer
|
||||
if (!int.TryParse(contentLengthString, out expectedContentLength))
|
||||
{
|
||||
throw new MessageParseException("", SR.HostingHeaderMissingContentLengthValue);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// The content length was invalid or missing. Trash the buffer we've read
|
||||
ShiftBufferBytesAndShrink(scanOffset + 4);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Skip past the headers plus the newline characters
|
||||
readOffset += scanOffset + 4;
|
||||
|
||||
// Done reading headers, now read content
|
||||
CurrentState = ReadState.Content;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryReadMessageContent(out string messageContent)
|
||||
{
|
||||
messageContent = null;
|
||||
|
||||
// Do we have enough bytes to reach the expected length?
|
||||
if (bufferEndOffset - readOffset < expectedContentLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert the message contents to a string using the specified encoding
|
||||
messageContent = MessageEncoding.GetString(messageBuffer, readOffset, expectedContentLength);
|
||||
|
||||
readOffset += expectedContentLength;
|
||||
|
||||
// Done reading content, now look for headers for the next message
|
||||
CurrentState = ReadState.Headers;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ShiftBufferBytesAndShrink(int bytesToRemove)
|
||||
{
|
||||
// Create a new buffer that is shrunken by the number of bytes to remove
|
||||
// Note: by using Max, we can guarantee a buffer of at least default buffer size
|
||||
byte[] newBuffer = new byte[Math.Max(messageBuffer.Length - bytesToRemove, DefaultBufferSize)];
|
||||
|
||||
// If we need to do shifting, do the shifting
|
||||
if (bytesToRemove <= messageBuffer.Length)
|
||||
{
|
||||
// Copy the existing buffer starting at the offset to remove
|
||||
Buffer.BlockCopy(messageBuffer, bytesToRemove, newBuffer, 0, bufferEndOffset - bytesToRemove);
|
||||
}
|
||||
|
||||
// Make the new buffer the message buffer
|
||||
messageBuffer = newBuffer;
|
||||
|
||||
// Reset the read offset and the end offset
|
||||
readOffset = 0;
|
||||
bufferEndOffset -= bytesToRemove;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
60
external/Microsoft.SqlTools.Hosting.v2/Protocol/MessageWriter.cs
vendored
Normal file
60
external/Microsoft.SqlTools.Hosting.v2/Protocol/MessageWriter.cs
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
public class MessageWriter
|
||||
{
|
||||
private const string ContentLength = "Content-Length: ";
|
||||
private const string ContentType = "Content-Type: application/json";
|
||||
private const string HeaderSeparator = "\r\n";
|
||||
private const string HeaderEnd = "\r\n\r\n";
|
||||
|
||||
private readonly Stream outputStream;
|
||||
private readonly AsyncLock writeLock = new AsyncLock();
|
||||
|
||||
public MessageWriter(Stream outputStream)
|
||||
{
|
||||
Validate.IsNotNull("streamWriter", outputStream);
|
||||
|
||||
this.outputStream = outputStream;
|
||||
}
|
||||
|
||||
public virtual async Task WriteMessage(Message messageToWrite)
|
||||
{
|
||||
Validate.IsNotNull("messageToWrite", messageToWrite);
|
||||
|
||||
// Log the JSON representation of the message
|
||||
string logMessage = string.Format("Sending message of type[{0}] and method[{1}]",
|
||||
messageToWrite.MessageType, messageToWrite.Method);
|
||||
Logger.Write(TraceEventType.Verbose, logMessage);
|
||||
|
||||
string serializedMessage = messageToWrite.Serialize();
|
||||
// TODO: Allow encoding to be passed in
|
||||
byte[] messageBytes = Encoding.UTF8.GetBytes(serializedMessage);
|
||||
|
||||
string headers = ContentLength + messageBytes.Length + HeaderSeparator
|
||||
+ ContentType + HeaderEnd;
|
||||
byte[] headerBytes = Encoding.ASCII.GetBytes(headers);
|
||||
|
||||
// Make sure only one call is writing at a time. You might be thinking
|
||||
// "Why not use a normal lock?" We use an AsyncLock here so that the
|
||||
// message loop doesn't get blocked while waiting for I/O to complete.
|
||||
using (await writeLock.LockAsync())
|
||||
{
|
||||
// Send the message
|
||||
await outputStream.WriteAsync(headerBytes, 0, headerBytes.Length);
|
||||
await outputStream.WriteAsync(messageBytes, 0, messageBytes.Length);
|
||||
await outputStream.FlushAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
external/Microsoft.SqlTools.Hosting.v2/Protocol/RequestContext.cs
vendored
Normal file
57
external/Microsoft.SqlTools.Hosting.v2/Protocol/RequestContext.cs
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.SqlTools.Hosting.Contracts;
|
||||
using Microsoft.SqlTools.Hosting.Contracts.Internal;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Protocol
|
||||
{
|
||||
public class RequestContext<TResult> : IEventSender
|
||||
{
|
||||
internal readonly BlockingCollection<Message> messageQueue;
|
||||
internal readonly Message requestMessage;
|
||||
|
||||
public RequestContext(Message message, BlockingCollection<Message> outgoingMessageQueue)
|
||||
{
|
||||
// TODO: Either 1) make this constructor internal and provide a tes framework for validating
|
||||
// or 2) extract an interface for requestcontext to allow users to mock
|
||||
requestMessage = message;
|
||||
messageQueue = outgoingMessageQueue;
|
||||
}
|
||||
|
||||
public virtual void SendResult(TResult resultDetails)
|
||||
{
|
||||
Message message = Message.CreateResponse(requestMessage.Id, resultDetails);
|
||||
messageQueue.Add(message);
|
||||
}
|
||||
|
||||
public virtual void SendEvent<TParams>(EventType<TParams> eventType, TParams eventParams)
|
||||
{
|
||||
Message message = Message.CreateEvent(eventType, eventParams);
|
||||
messageQueue.Add(message);
|
||||
}
|
||||
|
||||
public virtual void SendError(string errorMessage, int errorCode = 0)
|
||||
{
|
||||
// Build the error message
|
||||
Error error = new Error
|
||||
{
|
||||
Message = errorMessage,
|
||||
Code = errorCode
|
||||
};
|
||||
Message message = Message.CreateResponseError(requestMessage.Id, error);
|
||||
messageQueue.Add(message);
|
||||
}
|
||||
|
||||
public virtual void SendError(Exception e)
|
||||
{
|
||||
// Overload to use the parameterized error handler
|
||||
SendError(e.Message, e.HResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user