Serve.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. * Copyright 2024, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import ArgumentParser
  17. import Foundation
  18. import GRPCCore
  19. import GRPCNIOTransportHTTP2
  20. import GRPCProtobuf
  21. import Synchronization
  22. struct Serve: AsyncParsableCommand {
  23. static let configuration = CommandConfiguration(abstract: "Starts a route-guide server.")
  24. @Option(help: "The port to listen on")
  25. var port: Int = 31415
  26. private func loadFeatures() throws -> [Routeguide_Feature] {
  27. guard let url = Bundle.module.url(forResource: "route_guide_db", withExtension: "json") else {
  28. throw RPCError(code: .internalError, message: "Couldn't find 'route_guide_db.json")
  29. }
  30. let data = try Data(contentsOf: url)
  31. return try Routeguide_Feature.array(fromJSONUTF8Bytes: data)
  32. }
  33. func run() async throws {
  34. let features = try self.loadFeatures()
  35. let transport = HTTP2ServerTransport.Posix(
  36. address: .ipv4(host: "127.0.0.1", port: self.port),
  37. transportSecurity: .plaintext
  38. )
  39. let server = GRPCServer(transport: transport, services: [RouteGuideService(features: features)])
  40. try await withThrowingDiscardingTaskGroup { group in
  41. group.addTask { try await server.serve() }
  42. let address = try await transport.listeningAddress
  43. print("server listening on \(address)")
  44. }
  45. }
  46. }
  47. struct RouteGuideService {
  48. /// Known features.
  49. private let features: [Routeguide_Feature]
  50. /// Notes recorded by clients.
  51. private let receivedNotes: Notes
  52. /// A thread-safe store for notes sent by clients.
  53. private final class Notes: Sendable {
  54. private let notes: Mutex<[Routeguide_RouteNote]>
  55. init() {
  56. self.notes = Mutex([])
  57. }
  58. /// Records a note and returns all other notes recorded at the same location.
  59. ///
  60. /// - Parameter receivedNote: A note to record.
  61. /// - Returns: Other notes recorded at the same location.
  62. func recordNote(_ receivedNote: Routeguide_RouteNote) -> [Routeguide_RouteNote] {
  63. return self.notes.withLock { notes in
  64. var notesFromSameLocation: [Routeguide_RouteNote] = []
  65. for note in notes {
  66. if note.location == receivedNote.location {
  67. notesFromSameLocation.append(note)
  68. }
  69. }
  70. notes.append(receivedNote)
  71. return notesFromSameLocation
  72. }
  73. }
  74. }
  75. /// Creates a new route guide service.
  76. /// - Parameter features: Known features.
  77. init(features: [Routeguide_Feature]) {
  78. self.features = features
  79. self.receivedNotes = Notes()
  80. }
  81. /// Returns the first feature found at the given location, if one exists.
  82. private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? {
  83. self.features.first {
  84. $0.location.latitude == latitude && $0.location.longitude == longitude
  85. }
  86. }
  87. }
  88. extension RouteGuideService: Routeguide_RouteGuide.SimpleServiceProtocol {
  89. func getFeature(
  90. request: Routeguide_Point,
  91. context: ServerContext
  92. ) async throws -> Routeguide_Feature {
  93. let feature = self.findFeature(
  94. latitude: request.latitude,
  95. longitude: request.longitude
  96. )
  97. if let feature {
  98. return feature
  99. } else {
  100. // No feature: return a feature with an empty name.
  101. let unknownFeature = Routeguide_Feature.with {
  102. $0.name = ""
  103. $0.location = .with {
  104. $0.latitude = request.latitude
  105. $0.longitude = request.longitude
  106. }
  107. }
  108. return unknownFeature
  109. }
  110. }
  111. func listFeatures(
  112. request: Routeguide_Rectangle,
  113. response: RPCWriter<Routeguide_Feature>,
  114. context: ServerContext
  115. ) async throws {
  116. let featuresWithinBounds = self.features.filter { feature in
  117. !feature.name.isEmpty && feature.isContained(by: request)
  118. }
  119. try await response.write(contentsOf: featuresWithinBounds)
  120. }
  121. func recordRoute(
  122. request: RPCAsyncSequence<Routeguide_Point, any Error>,
  123. context: ServerContext
  124. ) async throws -> Routeguide_RouteSummary {
  125. let startTime = ContinuousClock.now
  126. var pointsVisited = 0
  127. var featuresVisited = 0
  128. var distanceTravelled = 0.0
  129. var previousPoint: Routeguide_Point? = nil
  130. for try await point in request {
  131. pointsVisited += 1
  132. if self.findFeature(latitude: point.latitude, longitude: point.longitude) != nil {
  133. featuresVisited += 1
  134. }
  135. if let previousPoint {
  136. distanceTravelled += greatCircleDistance(from: previousPoint, to: point)
  137. }
  138. previousPoint = point
  139. }
  140. let duration = startTime.duration(to: .now)
  141. let summary = Routeguide_RouteSummary.with {
  142. $0.pointCount = Int32(pointsVisited)
  143. $0.featureCount = Int32(featuresVisited)
  144. $0.elapsedTime = Int32(duration.components.seconds)
  145. $0.distance = Int32(distanceTravelled)
  146. }
  147. return summary
  148. }
  149. func routeChat(
  150. request: RPCAsyncSequence<Routeguide_RouteNote, any Error>,
  151. response: RPCWriter<Routeguide_RouteNote>,
  152. context: ServerContext
  153. ) async throws {
  154. for try await note in request {
  155. let notes = self.receivedNotes.recordNote(note)
  156. try await response.write(contentsOf: notes)
  157. }
  158. }
  159. }
  160. extension Routeguide_Feature {
  161. func isContained(
  162. by rectangle: Routeguide_Rectangle
  163. ) -> Bool {
  164. return rectangle.lo.latitude <= self.location.latitude
  165. && self.location.latitude <= rectangle.hi.latitude
  166. && rectangle.lo.longitude <= self.location.longitude
  167. && self.location.longitude <= rectangle.hi.longitude
  168. }
  169. }
  170. private func greatCircleDistance(
  171. from point1: Routeguide_Point,
  172. to point2: Routeguide_Point
  173. ) -> Double {
  174. // See https://en.wikipedia.org/wiki/Great-circle_distance
  175. //
  176. // Let λ1 (lambda1) and φ1 (phi1) be the longitude and latitude of point 1.
  177. // Let λ2 (lambda2) and φ2 (phi2) be the longitude and latitude of point 2.
  178. //
  179. // Let Δλ = λ2 - λ1, and Δφ = φ2 - φ1.
  180. //
  181. // The central angle between them, σc (sigmaC) can be computed as:
  182. //
  183. // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2))
  184. //
  185. // The unit distance (d) between point1 and point2 can then be computed as:
  186. //
  187. // d = 2 ⨯ atan(sqrt(σc), sqrt(1 - σc))
  188. let lambda1 = radians(degreesInE7: point1.longitude)
  189. let phi1 = radians(degreesInE7: point1.latitude)
  190. let lambda2 = radians(degreesInE7: point2.longitude)
  191. let phi2 = radians(degreesInE7: point2.latitude)
  192. // Δλ = λ2 - λ1
  193. let deltaLambda = lambda2 - lambda1
  194. // Δφ = φ2 - φ1
  195. let deltaPhi = phi2 - phi1
  196. // sin²(Δφ/2)
  197. let sinHalfDeltaPhiSquared = sin(deltaPhi / 2) * sin(deltaPhi / 2)
  198. // sin²(Δλ/2)
  199. let sinHalfDeltaLambdaSquared = sin(deltaLambda / 2) * sin(deltaLambda / 2)
  200. // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2))
  201. let sigmaC = 2 * sqrt(sinHalfDeltaPhiSquared + cos(phi1) * cos(phi2) * sinHalfDeltaLambdaSquared)
  202. // This is the unit distance, i.e. assumes the circle has a radius of 1.
  203. let unitDistance = 2 * atan2(sqrt(sigmaC), sqrt(1 - sigmaC))
  204. // Scale it by the radius of the Earth in meters.
  205. let earthRadius = 6_371_000.0
  206. return unitDistance * earthRadius
  207. }
  208. private func radians(degreesInE7 degrees: Int32) -> Double {
  209. return (Double(degrees) / 1e7) * .pi / 180.0
  210. }