2019年4月7日日曜日

画像圧縮ツールの「あっしゅくま」さん

JPEGとPNG画像を圧縮するツールを作ったので紹介します!

その名も「あっしゅくま」さんです。


その前に...! 今まで使っていたツールの紹介をさせていただきたいと思います。
私はJPEGとPNG画像を圧縮するときは、tinypngさんにお世話になっていました。
あの有名なパンダさんです。

何が良いかというと
  • ブラウザ上で使えてドロップ&ドロップだけで簡単
  • 複数枚を一括で圧縮できる
  • 圧縮率も問題なし。PNGは単純に圧縮だけじゃなく減色もしてくれてる。
 といった感じでとても便利です!

ただし、
  • 圧縮率の調整したい(もうちょっと容量を小さくしたいとか)
  • サーバーに画像をアップロードするのでプライバシーが絡む画像は少し抵抗がある
  • 圧縮後の画像の確認したい
みたいなことは弱点になってます。

ということで以上も解決した「あっしゅくま」さんの出番です

あっしゅくまさんは、Web上で簡単に画像の容量を小さくできる画像圧縮ツールです。
おさらいになってしまいますが、特徴を上げると
  • ブラウザ上で使えてドロップ&ドロップだけで簡単
  • 複数枚を一括で圧縮
  • 圧縮率の調整(もう少し品質を犠牲にしてでも小さくしたいとか、その逆も)
  • 圧縮をローカルで行うのでプライバシーが絡む画像や通信容量も安心
  • 保存する前に圧縮後の画像を確認可能
  • 画面幅に合わせたリサイズ
といったところでしょうか。

個人的に推したいところは
  • 圧縮をローカルで行うのでプライバシーが絡む画像も抵抗がいらない
  • 圧縮率も本格性能(mozjpegやoptpngを裏側で使っていてSquoosh同等)

SquooshはGoogleさんがオープンソースで公開している画像圧縮ツールです。
あっしゅくまの圧縮性能はSquoosh同等で、Squooshではできないできない複数枚の一括圧縮もできるので、そういったことが必要であれば是非あっしゅくまをお使いいただければと思います。



フリーなのでお気軽にご活用ください!

あっしゅくまはこちら



2018年2月22日木曜日

ELBの最適なタイムアウト設定

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月21日水曜日

iOSアプリ 端末時間をズラしてるとCookieの有効期限が...!

クッキーってこんな感じやん
 
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

同じ現象で困ってる方の助けになれば幸いでございます!
と、ほかの解決方法探し中...!

構成
  • 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)
//    }

}

しかし、今度はローカルサーバーで認証をかけてないページでセッションクッキーが吐かれなくなった(;´Д`)

こちらは未解決。

一旦本番では問題なくクッキー履かれていいるのでまた今度。

もし解決方法見つけたら、く、ください !

参考

2013年8月11日日曜日

JavaのJsonライブラリ


速くて、変換も簡単という噂のJsonを扱うライブラリだそうです!
その名もJackson!!

http://wiki.fasterxml.com/JacksonDownload

2013年8月10日土曜日

ActionBarSherlockが便利なん!

ActionBarSherlockが便利です!
ActionBarSherlockてのは古いOSでも4.0とかみたいなActionBarありのUI作れてしまうの!
作者さんに感謝過ぎます。


↓のActionBarの部分はSherlockです!



2013年4月16日火曜日

ボイスレコーダー公開しました!

「Chiee! 音声レコーダー」をGoolePlayで公開しました!

新しくボイスレコーダを作りました。Androidアプリです。
Androidのボイスレコーダの定番を目指したい(>_<) (←あくまで願望


当然、録音できます。
加えて、再生速度変えたり、逆再生できます。
使えるサンプリングレートは端末に依存するでしょう。

とりあえず、プロトタイプって感じですね。

まだ機能少ないですし。。。
地道に機能増やしてきます。

お歌の練習とか、会議の録音とか。
逆再生で逆さ言葉の実験したり、いろいろ使えるかと。

それと、ごめんなさい。
いろいろ、ごにょごにょしてるうちに、
デザインがごちゃごちゃしてきちゃいました!!!

どうしましょうか!


 


プログラミング的なことをいうと、録音はAudioRecord、再生はAudioTrackを使用して、
PCM的なことしてます。
なので保存ファイルはwavです。