Skip to content

Commit 91b1961

Browse files
Merge pull request #12 from Lercher/omnisharp-content-length
BugFix: content-length is counted in bytes
2 parents 937b3ec + ba38b4c commit 91b1961

File tree

7 files changed

+82
-34
lines changed

7 files changed

+82
-34
lines changed

sample/SampleServer/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ static async Task MainAsync(string[] args)
2424
// await Task.Delay(100);
2525
//}
2626

27-
var server = new LanguageServer(Console.In, Console.Out);
27+
var server = new LanguageServer(Console.OpenStandardInput(), Console.OpenStandardOutput());
2828

2929
server.AddHandler(new TextDocumentHandler(server));
3030

src/JsonRpc/Connection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class Connection : IDisposable
1818
private readonly IRequestRouter _requestRouter;
1919

2020
public Connection(
21-
TextReader input,
21+
Stream input,
2222
IOutputHandler outputHandler,
2323
IReciever reciever,
2424
IRequestProcessIdentifier requestProcessIdentifier,

src/JsonRpc/InputHandler.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class InputHandler : IInputHandler
1616
public static char[] HeaderKeys = { CR, LF, ':' };
1717
public const short MinBuffer = 21; // Minimum size of the buffer "Content-Length: X\r\n\r\n"
1818

19-
private readonly TextReader _input;
19+
private readonly Stream _input;
2020
private readonly IOutputHandler _outputHandler;
2121
private readonly IReciever _reciever;
2222
private readonly IRequestProcessIdentifier _requestProcessIdentifier;
@@ -26,14 +26,15 @@ public class InputHandler : IInputHandler
2626
private readonly IScheduler _scheduler;
2727

2828
public InputHandler(
29-
TextReader input,
29+
Stream input,
3030
IOutputHandler outputHandler,
3131
IReciever reciever,
3232
IRequestProcessIdentifier requestProcessIdentifier,
3333
IRequestRouter requestRouter,
3434
IResponseRouter responseRouter
3535
)
3636
{
37+
if (!input.CanRead) throw new ArgumentException($"must provide a readable stream for {nameof(input)}", nameof(input));
3738
_input = input;
3839
_outputHandler = outputHandler;
3940
_reciever = reciever;
@@ -54,23 +55,26 @@ public void Start()
5455

5556
private async void ProcessInputStream()
5657
{
58+
// header is encoded in ASCII
59+
// "Content-Length: 0" counts bytes for the following content
60+
// content is encoded in UTF-8
5761
while (true)
5862
{
5963
if (_inputThread == null) return;
6064

61-
var buffer = new char[300];
62-
var current = await _input.ReadBlockAsync(buffer, 0, MinBuffer);
65+
var buffer = new byte[300];
66+
var current = await _input.ReadAsync(buffer, 0, MinBuffer);
6367
if (current == 0) return; // no more _input
64-
65-
while (current < MinBuffer || buffer[current - 4] != CR || buffer[current - 3] != LF ||
68+
while (current < MinBuffer ||
69+
buffer[current - 4] != CR || buffer[current - 3] != LF ||
6670
buffer[current - 2] != CR || buffer[current - 1] != LF)
6771
{
68-
var n = await _input.ReadBlockAsync(buffer, current, 1);
72+
var n = await _input.ReadAsync(buffer, current, 1);
6973
if (n == 0) return; // no more _input, mitigates endless loop here.
7074
current += n;
7175
}
7276

73-
var headersContent = new string(buffer, 0, current);
77+
var headersContent = System.Text.Encoding.ASCII.GetString(buffer, 0, current);
7478
var headers = headersContent.Split(HeaderKeys, StringSplitOptions.RemoveEmptyEntries);
7579
long length = 0;
7680
for (var i = 1; i < headers.Length; i += 2)
@@ -91,15 +95,16 @@ private async void ProcessInputStream()
9195
}
9296
else
9397
{
94-
var requestBuffer = new char[length];
98+
var requestBuffer = new byte[length];
9599
var received = 0;
96100
while (received < length)
97101
{
98-
var n = await _input.ReadBlockAsync(requestBuffer, received, requestBuffer.Length - received);
102+
var n = await _input.ReadAsync(requestBuffer, received, requestBuffer.Length - received);
99103
if (n == 0) return; // no more _input
100104
received += n;
101105
}
102-
var payload = new string(requestBuffer);
106+
// TODO sometimes: encoding should be based on the respective header (including the wrong "utf8" value)
107+
var payload = System.Text.Encoding.UTF8.GetString(requestBuffer);
103108
HandleRequest(payload);
104109
}
105110
}

src/JsonRpc/OutputHandler.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ namespace JsonRpc
99
{
1010
public class OutputHandler : IOutputHandler
1111
{
12-
private readonly TextWriter _output;
12+
private readonly Stream _output;
1313
private readonly Thread _thread;
1414
private readonly BlockingCollection<object> _queue;
1515
private readonly CancellationTokenSource _cancel;
1616

17-
public OutputHandler(TextWriter output)
17+
public OutputHandler(Stream output)
1818
{
19+
if (!output.CanWrite) throw new ArgumentException($"must provide a writable stream for {nameof(output)}", nameof(output));
1920
_output = output;
2021
_queue = new BlockingCollection<object>();
2122
_cancel = new CancellationTokenSource();
@@ -42,13 +43,21 @@ private void ProcessOutputQueue()
4243
if (_queue.TryTake(out var value, Timeout.Infinite, token))
4344
{
4445
var content = JsonConvert.SerializeObject(value);
46+
var contentBytes = System.Text.Encoding.UTF8.GetBytes(content);
4547

4648
// TODO: Is this lsp specific??
4749
var sb = new StringBuilder();
48-
sb.Append($"Content-Length: {content.Length}\r\n");
50+
sb.Append($"Content-Length: {contentBytes.Length}\r\n");
4951
sb.Append($"\r\n");
50-
sb.Append(content);
51-
_output.Write(sb.ToString());
52+
var headerBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
53+
54+
// only one write to _output
55+
using (var ms = new MemoryStream(headerBytes.Length + contentBytes.Length))
56+
{
57+
ms.Write(headerBytes, 0, headerBytes.Length);
58+
ms.Write(contentBytes, 0, contentBytes.Length);
59+
_output.Write(ms.ToArray(), 0, (int)ms.Position);
60+
}
5261
}
5362
}
5463
}

src/Lsp/LanguageServer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ public class LanguageServer : ILanguageServer, IInitializeHandler, IInitializedH
2929
private readonly TaskCompletionSource<InitializeResult> _initializeComplete = new TaskCompletionSource<InitializeResult>();
3030
private readonly CompositeDisposable _disposable = new CompositeDisposable();
3131

32-
public LanguageServer(TextReader input, TextWriter output)
32+
public LanguageServer(Stream input, Stream output)
3333
: this(input, new OutputHandler(output), new LspReciever(), new RequestProcessIdentifier())
3434
{
3535
}
3636

3737
internal LanguageServer(
38-
TextReader input,
38+
Stream input,
3939
IOutputHandler output,
4040
LspReciever reciever,
4141
IRequestProcessIdentifier requestProcessIdentifier

test/JsonRpc.Tests/InputHandlerTests.cs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace JsonRpc.Tests
1919
public class InputHandlerTests
2020
{
2121
private static InputHandler NewHandler(
22-
TextReader inputStream,
22+
Stream inputStream,
2323
IOutputHandler outputHandler,
2424
IReciever reciever,
2525
IRequestProcessIdentifier requestProcessIdentifier,
@@ -53,7 +53,7 @@ public void ShouldPassInRequests()
5353
var reciever = Substitute.For<IReciever>();
5454

5555
using (NewHandler(
56-
new StreamReader(inputStream),
56+
inputStream,
5757
outputHandler,
5858
reciever,
5959
Substitute.For<IRequestProcessIdentifier>(),
@@ -70,6 +70,32 @@ public void ShouldPassInRequests()
7070
}
7171
}
7272

73+
[Fact]
74+
public void ShouldPassInUtf8EncodedRequests()
75+
{
76+
// Note: an ä (&auml;) is encoded by two bytes, so string-length is 13 and byte-length is 14
77+
var inputStream = new MemoryStream(Encoding.UTF8.GetBytes("Content-Length: 14\r\n\r\n{\"utf8\": \"ä\"}"));
78+
var outputHandler = Substitute.For<IOutputHandler>();
79+
var reciever = Substitute.For<IReciever>();
80+
81+
using (NewHandler(
82+
inputStream,
83+
outputHandler,
84+
reciever,
85+
Substitute.For<IRequestProcessIdentifier>(),
86+
Substitute.For<IRequestRouter>(),
87+
Substitute.For<IResponseRouter>(),
88+
cts => {
89+
reciever.When(x => x.IsValid(Arg.Any<JToken>()))
90+
.Do(x => {
91+
cts.Cancel();
92+
});
93+
}))
94+
{
95+
reciever.Received().IsValid(Arg.Is<JToken>(x => x["utf8"].ToString() == "ä"));
96+
}
97+
}
98+
7399
[Fact]
74100
public void ShouldHandleRequest()
75101
{
@@ -89,7 +115,7 @@ public void ShouldHandleRequest()
89115
.Returns(response);
90116

91117
using (NewHandler(
92-
new StreamReader(inputStream),
118+
inputStream,
93119
outputHandler,
94120
reciever,
95121
Substitute.For<IRequestProcessIdentifier>(),
@@ -121,7 +147,7 @@ public void ShouldHandleError()
121147

122148

123149
using (NewHandler(
124-
new StreamReader(inputStream),
150+
inputStream,
125151
outputHandler,
126152
reciever,
127153
Substitute.For<IRequestProcessIdentifier>(),
@@ -152,7 +178,7 @@ public void ShouldHandleNotification()
152178
.Returns(c => (new Renor[] { notification }, false));
153179

154180
using (NewHandler(
155-
new StreamReader(inputStream),
181+
inputStream,
156182
outputHandler,
157183
reciever,
158184
Substitute.For<IRequestProcessIdentifier>(),
@@ -186,7 +212,7 @@ public void ShouldHandleResponse()
186212
responseRouter.GetRequest(1L).Returns(tcs);
187213

188214
using (NewHandler(
189-
new StreamReader(inputStream),
215+
inputStream,
190216
outputHandler,
191217
reciever,
192218
Substitute.For<IRequestProcessIdentifier>(),

test/JsonRpc.Tests/OutputHandlerTests.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,21 @@
33
using System.Threading;
44
using System.Threading.Tasks;
55
using NSubstitute;
6+
using FluentAssertions;
67
using Xunit;
78

89
namespace JsonRpc.Tests
910
{
1011
public class OutputHandlerTests
1112
{
12-
private static (OutputHandler handler, Func<Task> wait) NewHandler(TextWriter textWriter,Action<CancellationTokenSource> action)
13+
private static (OutputHandler handler, Func<Task> wait) NewHandler(Stream Writer,Action<CancellationTokenSource> action)
1314
{
1415
var cts = new CancellationTokenSource();
1516
if (!System.Diagnostics.Debugger.IsAttached)
1617
cts.CancelAfter(TimeSpan.FromSeconds(120));
1718
action(cts);
1819

19-
var handler = new OutputHandler(textWriter);
20+
var handler = new OutputHandler(Writer);
2021
handler.Start();
2122
return (handler, () => {
2223
cts.Wait();
@@ -27,11 +28,16 @@ private static (OutputHandler handler, Func<Task> wait) NewHandler(TextWriter te
2728
[Fact]
2829
public async Task ShouldSerializeValues()
2930
{
30-
var tw = Substitute.For<TextWriter>();
31+
var w = Substitute.For<Stream>();
32+
var received = "";
33+
w.CanWrite.Returns(true);
3134

32-
var (handler, wait) = NewHandler(tw, cts => {
33-
tw.When(x => x.Write(Arg.Any<string>()))
34-
.Do(c => cts.Cancel());
35+
var (handler, wait) = NewHandler(w, cts => {
36+
w.When(x => x.Write(Arg.Any<byte[]>(), Arg.Any<int>(), Arg.Any<int>()))
37+
.Do(c => {
38+
received = System.Text.Encoding.UTF8.GetString(c.ArgAt<byte[]>(0), 0, c.ArgAt<int>(2));
39+
cts.Cancel();
40+
});
3541
});
3642
var value = new JsonRpc.Client.Response(1);
3743

@@ -40,8 +46,10 @@ public async Task ShouldSerializeValues()
4046

4147
handler.Send(value);
4248
await wait();
43-
44-
tw.Received().Write("Content-Length: 46\r\n\r\n{\"protocolVersion\":\"2.0\",\"id\":1,\"result\":null}");
49+
const string send = "Content-Length: 46\r\n\r\n{\"protocolVersion\":\"2.0\",\"id\":1,\"result\":null}";
50+
received.Should().Be(send);
51+
var b = System.Text.Encoding.UTF8.GetBytes(send);
52+
w.Received().Write(Arg.Any<byte[]>(), 0, b.Length); // can't compare b here, because it is only value-equal and this test tests reference equality
4553
}
4654
}
4755
}

0 commit comments

Comments
 (0)