ELBのクラッシックロードバランサのタイムアウトの話
デフォルトで60秒のアイドルタイムアウトが設定されています。
アイドルタイムアウトは、1バイトも送受信が行われないコネクションを何秒で切断するかの設定です。
長いアップロードなどでで継続的にデータが行われる場合は問題ないです。
送受信が行われないときのタイムアウトです。
例えば、サーバーがレスポンスなくタイムアウト値の時間を超えると切断する。
apacheの設定でのタイムアウトの最適化の話
/etc/httpd/conf/httpd.conf
apacheのタイムアウト > ELBのアイドルタイムアウト
基本的にこちらのタイムアウトは、ELBのタイムアウトより大きくするのが良い。
普通は、長い接続がパフォーマンスを悪くするので、
短いタイムアウトを設定するが
ロードバランサーがそこは面倒をみてくれるので短くなくてよい。
むしろロードバランサーはサーバーとの接続を維持したほうが、サーバーとのハンドシェイクなどのコストが減りパフォーマンスが良くなる可能性が高い。
KeepAliveとTimeoutをELBのアイドルタイムアウトの+60sや2倍などにするとよい。
"Timeout"
おすすめの値:120
ELBのidletimeoutが60sの場合
idletimeout+60sとか2倍とかかな
"KeepAlive"
おすすめの値:On
"KeepAliveTimeout"
おすすめの値:120
ELBのidletimeoutが60sの場合
idletimeout+60sとか2倍とかかな
"MaxKeepAliveTimeout"
おすすめの値:100
参考
https://aws.amazon.com/premiumsupport/knowledge-center/apache-backend-elb/
2018年2月22日木曜日
2018年2月21日水曜日
iOSアプリ 端末時間をズラしてるとCookieの有効期限が...!
クッキーってこんな感じやん
で、有効期限ってWed, 11 Oct 2018 07:28:00 GMTみたいに日付で指定されてるんだけど、この日時ってどこの時間をみてると思う?
この日時ってiOSアプリでは端末の時間で見てるみたいなんだよ。
つまりよ、クッキーの有効期限を10分後にしてて、iPhoneの端末時間を10分以上進めてるとするよ。
そうすると....
一瞬でクッキーが溶けていくーーー(´°̥̥̥̥̥̥̥̥ω°̥̥̥̥̥̥̥̥`)
ちなみに、Androidだとサーバーの時間との相対的な日時で見てくれてるみたいで、クッキーは溶けず、問題にならない。
クライアントによって解釈が違うのよね。
PCのブラウザも解釈がそれぞれ違う模様。
ということでiOSでクッキーを受け取ったら、なんとかしてサーバーとの相対時間で有効期限を設定して保持したい。
iOSでクッキーを管理してるのは、HTTPCookieStorage
HTTPCookieStorageを改造してクッキー保存するときに有効期限を相対的な値に書き換える作戦で行ってみる
CustomHTTPCookieStorage
はい、やってみましたが、
HTTPCookieStorageを差し替えた途端、呼ばれなくなりました笑
次の作戦。
クッキーを受け取ったのを監視して、受け取った瞬間に有効期限を書き換える。
あ、、、動かしてみてから気づく。
HTTPCookieStorageに入った瞬間消えてるやん....
あきらめて、サーバー側でクッキーの有効期限をそもそも伸ばしました。
Set-Cookie: AccessToken=a3fWa039adnaknd; Expires=Wed, 11 Oct 2018 07:28:00 GMT; Secure; HttpOnly
で、有効期限ってWed, 11 Oct 2018 07:28:00 GMTみたいに日付で指定されてるんだけど、この日時ってどこの時間をみてると思う?
この日時ってiOSアプリでは端末の時間で見てるみたいなんだよ。
つまりよ、クッキーの有効期限を10分後にしてて、iPhoneの端末時間を10分以上進めてるとするよ。
そうすると....
一瞬でクッキーが溶けていくーーー(´°̥̥̥̥̥̥̥̥ω°̥̥̥̥̥̥̥̥`)
ちなみに、Androidだとサーバーの時間との相対的な日時で見てくれてるみたいで、クッキーは溶けず、問題にならない。
クライアントによって解釈が違うのよね。
PCのブラウザも解釈がそれぞれ違う模様。
ということでiOSでクッキーを受け取ったら、なんとかしてサーバーとの相対時間で有効期限を設定して保持したい。
iOSでクッキーを管理してるのは、HTTPCookieStorage
HTTPCookieStorageを改造してクッキー保存するときに有効期限を相対的な値に書き換える作戦で行ってみる
CustomHTTPCookieStorage
class CustomHTTPCookieStorage : HTTPCookieStorage { private let source: HTTPCookieStorage init(source: HTTPCookieStorage) { self.source = source } /*! @abstract Get all the cookies @result An NSArray of NSHTTPCookies */ override var cookies: [HTTPCookie]? { get { print("CustomHTTPCookieStorage.cookies -> \(self.source.cookies?.count.description ?? "nil")") return self.source.cookies } } /*! @method setCookie: @abstract Set a cookie @discussion The cookie will override an existing cookie with the same name, domain and path, if any. */ override func setCookie(_ cookie: HTTPCookie) { print("CustomHTTPCookieStorage.setCookie", cookie) //super.setCookie(cookie) self.source.setCookie(cookie) } /*! @method deleteCookie: @abstract Delete the specified cookie */ override func deleteCookie(_ cookie: HTTPCookie) { print("CustomHTTPCookieStorage.deleteCookie", cookie) //super.deleteCookie(cookie) self.source.deleteCookie(cookie) } /*! @method removeCookiesSince: @abstract Delete all cookies from the cookie storage since the provided date. */ @available(iOS 8.0, *) override func removeCookies(since date: Date) { print("CustomHTTPCookieStorage.removeCookies", date) //super.removeCookies(since: date) self.source.removeCookies(since: date) } /*! @method cookiesForURL: @abstract Returns an array of cookies to send to the given URL. @param URL The URL for which to get cookies. @result an NSArray of NSHTTPCookie objects. @discussion The cookie manager examines the cookies it stores and includes those which should be sent to the given URL. You can use <tt>+[NSCookie requestHeaderFieldsWithCookies:]</tt> to turn this array into a set of header fields to add to a request. */ override func cookies(for URL: URL) -> [HTTPCookie]? { print("CustomHTTPCookieStorage.cookies for URL \(URL) -> \(self.source.cookies?.count.description ?? "nil")") return self.source.cookies(for: URL) //return super.cookies(for: URL) } /*! @method setCookies:forURL:mainDocumentURL: @abstract Adds an array cookies to the cookie store, following the cookie accept policy. @param cookies The cookies to set. @param URL The URL from which the cookies were sent. @param mainDocumentURL The main document URL to be used as a base for the "same domain as main document" policy. @discussion For mainDocumentURL, the caller should pass the URL for an appropriate main document, if known. For example, when loading a web page, the URL of the main html document for the top-level frame should be passed. To save cookies based on a set of response headers, you can use <tt>+[NSCookie cookiesWithResponseHeaderFields:forURL:]</tt> on a header field dictionary and then use this method to store the resulting cookies in accordance with policy settings. */ override func setCookies(_ cookies: [HTTPCookie], for URL: URL?, mainDocumentURL: URL?) { print("CustomHTTPCookieStorage.setCookies cookies for URL mainDocumentURL", cookies, URL ?? "nil", mainDocumentURL ?? "nil") //super.setCookies(cookies, for: URL, mainDocumentURL: mainDocumentURL) self.source.setCookies(cookies, for: URL, mainDocumentURL: mainDocumentURL) } /*! @abstract The cookie accept policy preference of the receiver. */ override var cookieAcceptPolicy: HTTPCookie.AcceptPolicy { get { return self.source.cookieAcceptPolicy } set { self.source.cookieAcceptPolicy = newValue } } /*! @method sortedCookiesUsingDescriptors: @abstract Returns an array of all cookies in the store, sorted according to the key value and sorting direction of the NSSortDescriptors specified in the parameter. @param sortOrder an array of NSSortDescriptors which represent the preferred sort order of the resulting array. @discussion proper sorting of cookies may require extensive string conversion, which can be avoided by allowing the system to perform the sorting. This API is to be preferred over the more generic -[NSHTTPCookieStorage cookies] API, if sorting is going to be performed. */ @available(iOS 5.0, *) override func sortedCookies(using sortOrder: [NSSortDescriptor]) -> [HTTPCookie] { print("CustomHTTPCookieStorage.sortedCookies", sortOrder) return self.source.sortedCookies(using: sortOrder) //return super.sortedCookies(using: sortOrder) } }
はい、やってみましたが、
HTTPCookieStorageを差し替えた途端、呼ばれなくなりました笑
次の作戦。
クッキーを受け取ったのを監視して、受け取った瞬間に有効期限を書き換える。
NotificationCenter.default.addObserver( self, selector: #selector(handleCookieManagerCookiesChanged(_:)), name: NSNotification.Name.NSHTTPCookieManagerCookiesChanged, object: nil)
@objc private func handleCookieManagerCookiesChanged (_ notification: Notification) { print("handleCookieManagerCookiesChanged", notification) guard let cs: HTTPCookieStorage = notification.object as? HTTPCookieStorage else { return } guard cs === self.manager.session.configuration.httpCookieStorage else { print("Different httpCookieStorage") return } self.adjustCookieExpires() }
extension A { func adjustCookieExpires () { guard let cs: HTTPCookieStorage = self.manager.session.configuration.httpCookieStorage else { return } adjustCookieExpires(cs: cs) } private func adjustCookieExpires (cs: HTTPCookieStorage) { print("adjustCookieExpires") for cookie : HTTPCookie in cs.cookies ?? [] { guard var properties: [HTTPCookiePropertyKey: Any] = cookie.properties else { print("cookie properties is nil") continue } var modifiled: Bool = false print("cookie properties = \(properties)") let cookieComment: String? = properties[HTTPCookiePropertyKey.comment] as? String print("cookie cookieComment = \(cookieComment ?? "nil")") if let expires: Date = properties[HTTPCookiePropertyKey.expires] as? Date { print("cookie expires = \(expires)") let adjusted: Bool = cookieComment?.contains(cookieExpiresAdjestedComment) ?? false if !adjusted { properties[HTTPCookiePropertyKey.expires] = expires.addingTimeInterval(60 * 60 * 24 * 30) let newComment: String if let oldComment: String = properties[HTTPCookiePropertyKey.comment] as? String { newComment = oldComment + " " + cookieExpiresAdjestedComment } else { newComment = cookieExpiresAdjestedComment } properties[HTTPCookiePropertyKey.comment] = newComment modifiled = true } } guard modifiled else { print("cookie not modifiled") continue } guard let newCookie: HTTPCookie = HTTPCookie(properties: properties) else { print("cookie newCookie is nil") continue } print("newCookie = \(newCookie)") cs.setCookie(newCookie) } } }
あ、、、動かしてみてから気づく。
HTTPCookieStorageに入った瞬間消えてるやん....
あきらめて、サーバー側でクッキーの有効期限をそもそも伸ばしました。
2018年2月17日土曜日
GAE x Spring Boot で Spring Security が上手く動かぬ
Google App Engine Java8 Standard x Spring Boot x Spring Security
一旦本番では問題なくクッキー履かれていいるのでまた今度。
もし解決方法見つけたら、く、ください !
参考
同じ現象で困ってる方の助けになれば幸いでございます!
と、ほかの解決方法探し中...!
構成
Devローカルサーバーならしっかり動いている。
本番環境で問題が発生する。
デプロイしてちゃんと起動はする。
しかし、Spring Securityを使ったフォームのログインで問題。
ログインボタンを押した後、
なにごとも無かったかのように、
ログイン画面に戻る笑
どうやらGAEのセッションハンドラを使うと上手く動かぬもよう。
GAEのセッションハンドラを使わず、
GAE内のmemcacheもしくはDatastoreを使って
自前のセッションハンドラにします。
DatastoreSessionRepository
しかし、今度はローカルサーバーで認証をかけてないページでセッションクッキーが吐かれなくなった(;´Д`)
こちらは未解決。と、ほかの解決方法探し中...!
構成
- Google App Engine Java 8 Standard
- Spring Boot 1.5.9
- Spring Security
- Kotlin
- Gradle
Devローカルサーバーならしっかり動いている。
本番環境で問題が発生する。
デプロイしてちゃんと起動はする。
しかし、Spring Securityを使ったフォームのログインで問題。
ログインボタンを押した後、
なにごとも無かったかのように、
ログイン画面に戻る笑
どうやらGAEのセッションハンドラを使うと上手く動かぬもよう。
GAEのセッションハンドラを使わず、
GAE内のmemcacheもしくはDatastoreを使って
自前のセッションハンドラにします。
DatastoreSessionRepository
package com.hometest.libs.appengine.session; import com.google.appengine.api.datastore.* import org.springframework.session.MapSession import org.springframework.session.SessionRepository import org.springframework.stereotype.Component import java.io.* import java.util.logging.Logger @Component class DatastoreSessionRepository(private val datastoreService: DatastoreService) : SessionRepository<MapSession> { private val logger = Logger.getLogger(javaClass.simpleName) private val maxInactiveIntervalInSeconds: Int = 3600 private val kind = "Session" private val propertyName = "serialized" override fun createSession(): MapSession = MapSession().also { session -> session.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds logger.info { "createSession() = ${session.id}" } } override fun save(session: MapSession) { logger.info { "save(${session.id}) with expiration ${session.maxInactiveIntervalInSeconds}" } ByteArrayOutputStream().use { byteArray -> ObjectOutputStream(byteArray).use { outStream -> outStream.writeObject(session) } datastoreService.put(Entity(kind, session.id).apply { // TODO: Check byteArray size. This byte array can be no bigger than 1MB. // https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/datastore/Blob setProperty(propertyName, Blob(byteArray.toByteArray())) }) } } override fun getSession(id: String): MapSession? { val blob: Blob? = try { datastoreService.get(KeyFactory.createKey(kind, id)) .getProperty(propertyName) as? Blob } catch (_: EntityNotFoundException) { null } val session: MapSession? = blob?.let { ByteArrayInputStream(it.bytes).use { byteArray -> ObjectInputStream(byteArray).use { inStream -> (inStream.readObject() as MapSession).also { session -> session.lastAccessedTime = System.currentTimeMillis() } } } } logger.info { "getSession($id) = ${session?.id}" } return session } override fun delete(id: String) { logger.info { "delete($id)" } datastoreService.delete(KeyFactory.createKey(kind, id)) } }MemcacheSessionRepository
package com.hometest.libs.appengine.session; import com.google.appengine.api.memcache.Expiration import com.google.appengine.api.memcache.MemcacheService import org.springframework.session.MapSession import org.springframework.session.SessionRepository import org.springframework.stereotype.Component import java.util.logging.Logger @Component class MemcacheSessionRepository(private val memcacheService: MemcacheService) : SessionRepository<MapSession> { private val logger = Logger.getLogger(javaClass.simpleName) private val maxInactiveIntervalInSeconds: Int = 3600 override fun createSession() = MapSession().also { session -> session.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds logger.info { "createSession() = ${session.id}" } } override fun save(session: MapSession) { logger.info { "save(${session.id}) with expiration ${session.maxInactiveIntervalInSeconds}" } memcacheService.put(session.id, session, Expiration.byDeltaSeconds(session.maxInactiveIntervalInSeconds)) } override fun getSession(id: String): MapSession? = (memcacheService.get(id) as? MapSession)?.also { session -> session.lastAccessedTime = System.currentTimeMillis() }.also { session -> logger.info { "getSession($id) = ${session?.id}" } } override fun delete(id: String) { logger.info { "delete($id)" } memcacheService.delete(id) } }AppengineConfiguration
package com.hometest.config; import com.google.appengine.api.datastore.DatastoreService import com.google.appengine.api.datastore.DatastoreServiceFactory import com.google.appengine.api.memcache.MemcacheService import com.google.appengine.api.memcache.MemcacheServiceFactory import com.hometest.libs.appengine.session.MemcacheSessionRepository import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.session.MapSession import org.springframework.session.web.http.SessionRepositoryFilter @Configuration class AppengineConfiguration { // AppEngine Session @Bean fun memcacheService(): MemcacheService { return MemcacheServiceFactory.getMemcacheService() } @Bean fun datastoreService(): DatastoreService { return DatastoreServiceFactory.getDatastoreService() } @Bean fun springSessionRepositoryFilter(sessionRepository: MemcacheSessionRepository): SessionRepositoryFilter<MapSession> { return SessionRepositoryFilter(sessionRepository) } // @Bean // fun springSessionRepositoryFilter(sessionRepository: DatastoreSessionRepository): SessionRepositoryFilter<MapSession> { // return SessionRepositoryFilter(sessionRepository) // } }
しかし、今度はローカルサーバーで認証をかけてないページでセッションクッキーが吐かれなくなった(;´Д`)
一旦本番では問題なくクッキー履かれていいるのでまた今度。
もし解決方法見つけたら、く、ください !
参考
- https://stackoverflow.com/questions/45217234/issue-with-using-spring-oauth-on-java8-standard-environment
- https://github.com/int128/gradleupdate/commit/2405310dd0da4e19cf4d4b55a16f8466c1d62cc8
- https://github.com/nosix/appengine-java8-spring-oauth2/blob/master/application/src/main/kotlin/org/musyozoku/appengine/session/DatastoreSessionRepository.kt
- https://gist.github.com/lilianaziolek/6851c2f81be81eae2207ec863f41f484
登録:
投稿 (Atom)